mirror of
https://github.com/luau-lang/luau.git
synced 2025-03-03 18:51:42 +00:00
Sync to upstream/release/660 (#1643)
# General This release introduces initial work on a Roundtrippable AST for Luau, and numerous fixes to the new type solver, runtime, and fragment autocomplete. ## Roundtrippable AST To support tooling around source code transformations, we are extending the parser to retain source information so that we can re-emit the initial source code exactly as the author wrote it. We have made numerous changes to the Transpiler, added new AST types such as `AstTypeGroup`, and added source information to AST nodes such as `AstExprInterpString`, `AstExprIfElse`, `AstTypeTable`, `AstTypeReference`, `AstTypeSingletonString`, and `AstTypeTypeof`. ## New Type Solver * Implement `setmetatable` and `getmetatable` type functions. * Fix handling of nested and recursive union type functions to prevent the solver from getting stuck. * Free types in both old and new solver now have an upper and lower bound to resolve mixed mode usage of the solvers in fragment autocomplete. * Fix infinite recursion during normalization of cyclic tables. * Add normalization support for intersections of subclasses with negated superclasses. ## Runtime * Fix compilation error in Luau buffer bit operations for big-endian machines. ## Miscellaneous * Add test and bugfixes to fragment autocomplete. * Fixed `clang-tidy` warnings in `Simplify.cpp`. **Full Changelog**: https://github.com/luau-lang/luau/compare/0.659...0.660 --- Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Talha Pathan <tpathan@roblox.com> Co-authored-by: Varun Saini <vsaini@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com> Co-authored-by: Menarul Alam <malam@roblox.com>
This commit is contained in:
parent
f8a1e0129d
commit
2e61028cba
73 changed files with 5870 additions and 519 deletions
|
@ -4,6 +4,7 @@
|
||||||
#include <Luau/NotNull.h>
|
#include <Luau/NotNull.h>
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/Scope.h"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
@ -26,13 +27,17 @@ struct CloneState
|
||||||
* while `clone` will make a deep copy of the entire type and its every component.
|
* while `clone` will make a deep copy of the entire type and its every component.
|
||||||
*
|
*
|
||||||
* Be mindful about which behavior you actually _want_.
|
* Be mindful about which behavior you actually _want_.
|
||||||
|
*
|
||||||
|
* Persistent types are not cloned as an optimization.
|
||||||
|
* If a type is cloned in order to mutate it, 'ignorePersistent' has to be set
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
|
||||||
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState);
|
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
|
||||||
|
|
||||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
||||||
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
|
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
|
||||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
|
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
|
||||||
|
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "Luau/ModuleResolver.h"
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/RequireTracer.h"
|
#include "Luau/RequireTracer.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
|
#include "Luau/Set.h"
|
||||||
#include "Luau/TypeCheckLimits.h"
|
#include "Luau/TypeCheckLimits.h"
|
||||||
#include "Luau/Variant.h"
|
#include "Luau/Variant.h"
|
||||||
#include "Luau/AnyTypeSummary.h"
|
#include "Luau/AnyTypeSummary.h"
|
||||||
|
@ -56,13 +57,32 @@ struct SourceNode
|
||||||
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
|
return forAutocomplete ? dirtyModuleForAutocomplete : dirtyModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasInvalidModuleDependency(bool forAutocomplete) const
|
||||||
|
{
|
||||||
|
return forAutocomplete ? invalidModuleDependencyForAutocomplete : invalidModuleDependency;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInvalidModuleDependency(bool value, bool forAutocomplete)
|
||||||
|
{
|
||||||
|
if (forAutocomplete)
|
||||||
|
invalidModuleDependencyForAutocomplete = value;
|
||||||
|
else
|
||||||
|
invalidModuleDependency = value;
|
||||||
|
}
|
||||||
|
|
||||||
ModuleName name;
|
ModuleName name;
|
||||||
std::string humanReadableName;
|
std::string humanReadableName;
|
||||||
DenseHashSet<ModuleName> requireSet{{}};
|
DenseHashSet<ModuleName> requireSet{{}};
|
||||||
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
||||||
|
Set<ModuleName> dependents{{}};
|
||||||
|
|
||||||
bool dirtySourceModule = true;
|
bool dirtySourceModule = true;
|
||||||
bool dirtyModule = true;
|
bool dirtyModule = true;
|
||||||
bool dirtyModuleForAutocomplete = true;
|
bool dirtyModuleForAutocomplete = true;
|
||||||
|
|
||||||
|
bool invalidModuleDependency = true;
|
||||||
|
bool invalidModuleDependencyForAutocomplete = true;
|
||||||
|
|
||||||
double autocompleteLimitsMult = 1.0;
|
double autocompleteLimitsMult = 1.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,7 +137,7 @@ struct FrontendModuleResolver : ModuleResolver
|
||||||
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
|
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
|
||||||
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override;
|
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override;
|
||||||
|
|
||||||
void setModule(const ModuleName& moduleName, ModulePtr module);
|
bool setModule(const ModuleName& moduleName, ModulePtr module);
|
||||||
void clearModules();
|
void clearModules();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -151,9 +171,13 @@ struct Frontend
|
||||||
// Parse and typecheck module graph
|
// Parse and typecheck module graph
|
||||||
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess
|
CheckResult check(const ModuleName& name, std::optional<FrontendOptions> optionOverride = {}); // new shininess
|
||||||
|
|
||||||
|
bool allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete = false) const;
|
||||||
|
|
||||||
bool isDirty(const ModuleName& name, bool forAutocomplete = false) const;
|
bool isDirty(const ModuleName& name, bool forAutocomplete = false) const;
|
||||||
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
|
void markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty = nullptr);
|
||||||
|
|
||||||
|
void traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree);
|
||||||
|
|
||||||
/** Borrow a pointer into the SourceModule cache.
|
/** Borrow a pointer into the SourceModule cache.
|
||||||
*
|
*
|
||||||
* Returns nullptr if we don't have it. This could mean that the script
|
* Returns nullptr if we don't have it. This could mean that the script
|
||||||
|
|
|
@ -19,10 +19,10 @@ struct SimplifyResult
|
||||||
DenseHashSet<TypeId> blockedTypes;
|
DenseHashSet<TypeId> blockedTypes;
|
||||||
};
|
};
|
||||||
|
|
||||||
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
|
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right);
|
||||||
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts);
|
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts);
|
||||||
|
|
||||||
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
|
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right);
|
||||||
|
|
||||||
enum class Relation
|
enum class Relation
|
||||||
{
|
{
|
||||||
|
|
|
@ -69,12 +69,16 @@ using Name = std::string;
|
||||||
// A free type is one whose exact shape has yet to be fully determined.
|
// A free type is one whose exact shape has yet to be fully determined.
|
||||||
struct FreeType
|
struct FreeType
|
||||||
{
|
{
|
||||||
|
// New constructors
|
||||||
|
explicit FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound);
|
||||||
|
// This one got promoted to explicit
|
||||||
|
explicit FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound);
|
||||||
|
explicit FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound);
|
||||||
|
// Old constructors
|
||||||
explicit FreeType(TypeLevel level);
|
explicit FreeType(TypeLevel level);
|
||||||
explicit FreeType(Scope* scope);
|
explicit FreeType(Scope* scope);
|
||||||
FreeType(Scope* scope, TypeLevel level);
|
FreeType(Scope* scope, TypeLevel level);
|
||||||
|
|
||||||
FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound);
|
|
||||||
|
|
||||||
int index;
|
int index;
|
||||||
TypeLevel level;
|
TypeLevel level;
|
||||||
Scope* scope = nullptr;
|
Scope* scope = nullptr;
|
||||||
|
|
|
@ -32,9 +32,13 @@ struct TypeArena
|
||||||
|
|
||||||
TypeId addTV(Type&& tv);
|
TypeId addTV(Type&& tv);
|
||||||
|
|
||||||
TypeId freshType(TypeLevel level);
|
TypeId freshType(NotNull<BuiltinTypes> builtins, TypeLevel level);
|
||||||
TypeId freshType(Scope* scope);
|
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
|
||||||
TypeId freshType(Scope* scope, TypeLevel level);
|
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level);
|
||||||
|
|
||||||
|
TypeId freshType_DEPRECATED(TypeLevel level);
|
||||||
|
TypeId freshType_DEPRECATED(Scope* scope);
|
||||||
|
TypeId freshType_DEPRECATED(Scope* scope, TypeLevel level);
|
||||||
|
|
||||||
TypePackId freshTypePack(Scope* scope);
|
TypePackId freshTypePack(Scope* scope);
|
||||||
|
|
||||||
|
|
|
@ -241,6 +241,9 @@ struct BuiltinTypeFunctions
|
||||||
TypeFunction indexFunc;
|
TypeFunction indexFunc;
|
||||||
TypeFunction rawgetFunc;
|
TypeFunction rawgetFunc;
|
||||||
|
|
||||||
|
TypeFunction setmetatableFunc;
|
||||||
|
TypeFunction getmetatableFunc;
|
||||||
|
|
||||||
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1161,6 +1161,19 @@ struct AstJsonEncoder : public AstVisitor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool visit(class AstTypeGroup* node) override
|
||||||
|
{
|
||||||
|
writeNode(
|
||||||
|
node,
|
||||||
|
"AstTypeGroup",
|
||||||
|
[&]()
|
||||||
|
{
|
||||||
|
write("type", node->type);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool visit(class AstTypeSingletonBool* node) override
|
bool visit(class AstTypeSingletonBool* node) override
|
||||||
{
|
{
|
||||||
writeNode(
|
writeNode(
|
||||||
|
|
|
@ -29,12 +29,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins2)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
|
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
|
||||||
LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2)
|
LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType2)
|
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -449,10 +448,9 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
ttv->props["foreachi"].deprecated = true;
|
ttv->props["foreachi"].deprecated = true;
|
||||||
|
|
||||||
attachMagicFunction(ttv->props["pack"].type(), std::make_shared<MagicPack>());
|
attachMagicFunction(ttv->props["pack"].type(), std::make_shared<MagicPack>());
|
||||||
if (FFlag::LuauTableCloneClonesType2)
|
if (FFlag::LuauTableCloneClonesType3)
|
||||||
attachMagicFunction(ttv->props["clone"].type(), std::make_shared<MagicClone>());
|
attachMagicFunction(ttv->props["clone"].type(), std::make_shared<MagicClone>());
|
||||||
if (FFlag::LuauTypestateBuiltins2)
|
attachMagicFunction(ttv->props["freeze"].type(), std::make_shared<MagicFreeze>());
|
||||||
attachMagicFunction(ttv->props["freeze"].type(), std::make_shared<MagicFreeze>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::AutocompleteRequirePathSuggestions2)
|
if (FFlag::AutocompleteRequirePathSuggestions2)
|
||||||
|
@ -613,10 +611,7 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
|
||||||
|
|
||||||
if (!fmt)
|
if (!fmt)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauStringFormatArityFix)
|
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location);
|
||||||
context.typechecker->reportError(
|
|
||||||
CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1401,7 +1396,7 @@ std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
|
||||||
WithPredicate<TypePackId> withPredicate
|
WithPredicate<TypePackId> withPredicate
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauTableCloneClonesType2);
|
LUAU_ASSERT(FFlag::LuauTableCloneClonesType3);
|
||||||
|
|
||||||
auto [paramPack, _predicates] = withPredicate;
|
auto [paramPack, _predicates] = withPredicate;
|
||||||
|
|
||||||
|
@ -1416,6 +1411,9 @@ std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
|
||||||
|
|
||||||
TypeId inputType = follow(paramTypes[0]);
|
TypeId inputType = follow(paramTypes[0]);
|
||||||
|
|
||||||
|
if (!get<TableType>(inputType))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
CloneState cloneState{typechecker.builtinTypes};
|
CloneState cloneState{typechecker.builtinTypes};
|
||||||
TypeId resultType = shallowClone(inputType, arena, cloneState);
|
TypeId resultType = shallowClone(inputType, arena, cloneState);
|
||||||
|
|
||||||
|
@ -1425,7 +1423,7 @@ std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
|
||||||
|
|
||||||
bool MagicClone::infer(const MagicFunctionCallContext& context)
|
bool MagicClone::infer(const MagicFunctionCallContext& context)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauTableCloneClonesType2);
|
LUAU_ASSERT(FFlag::LuauTableCloneClonesType3);
|
||||||
|
|
||||||
TypeArena* arena = context.solver->arena;
|
TypeArena* arena = context.solver->arena;
|
||||||
|
|
||||||
|
@ -1438,8 +1436,11 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
|
||||||
|
|
||||||
TypeId inputType = follow(paramTypes[0]);
|
TypeId inputType = follow(paramTypes[0]);
|
||||||
|
|
||||||
|
if (!get<TableType>(inputType))
|
||||||
|
return false;
|
||||||
|
|
||||||
CloneState cloneState{context.solver->builtinTypes};
|
CloneState cloneState{context.solver->builtinTypes};
|
||||||
TypeId resultType = shallowClone(inputType, *arena, cloneState);
|
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent);
|
||||||
|
|
||||||
if (auto tableType = getMutable<TableType>(resultType))
|
if (auto tableType = getMutable<TableType>(resultType))
|
||||||
{
|
{
|
||||||
|
@ -1475,7 +1476,7 @@ static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCa
|
||||||
{
|
{
|
||||||
// Clone the input type, this will become our final result type after we mutate it.
|
// Clone the input type, this will become our final result type after we mutate it.
|
||||||
CloneState cloneState{context.solver->builtinTypes};
|
CloneState cloneState{context.solver->builtinTypes};
|
||||||
TypeId resultType = shallowClone(inputType, *arena, cloneState);
|
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent);
|
||||||
auto tableTy = getMutable<TableType>(resultType);
|
auto tableTy = getMutable<TableType>(resultType);
|
||||||
// `clone` should not break this.
|
// `clone` should not break this.
|
||||||
LUAU_ASSERT(tableTy);
|
LUAU_ASSERT(tableTy);
|
||||||
|
@ -1507,8 +1508,6 @@ std::optional<WithPredicate<TypePackId>> MagicFreeze::handleOldSolver(struct Typ
|
||||||
|
|
||||||
bool MagicFreeze::infer(const MagicFunctionCallContext& context)
|
bool MagicFreeze::infer(const MagicFunctionCallContext& context)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::LuauTypestateBuiltins2);
|
|
||||||
|
|
||||||
TypeArena* arena = context.solver->arena;
|
TypeArena* arena = context.solver->arena;
|
||||||
const DataFlowGraph* dfg = context.solver->dfg.get();
|
const DataFlowGraph* dfg = context.solver->dfg.get();
|
||||||
Scope* scope = context.constraint->scope.get();
|
Scope* scope = context.constraint->scope.get();
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "Luau/Unifiable.h"
|
#include "Luau/Unifiable.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
|
||||||
|
|
||||||
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
|
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
||||||
|
@ -38,14 +39,26 @@ class TypeCloner
|
||||||
NotNull<SeenTypes> types;
|
NotNull<SeenTypes> types;
|
||||||
NotNull<SeenTypePacks> packs;
|
NotNull<SeenTypePacks> packs;
|
||||||
|
|
||||||
|
TypeId forceTy = nullptr;
|
||||||
|
TypePackId forceTp = nullptr;
|
||||||
|
|
||||||
int steps = 0;
|
int steps = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TypeCloner(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<SeenTypes> types, NotNull<SeenTypePacks> packs)
|
TypeCloner(
|
||||||
|
NotNull<TypeArena> arena,
|
||||||
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
|
NotNull<SeenTypes> types,
|
||||||
|
NotNull<SeenTypePacks> packs,
|
||||||
|
TypeId forceTy,
|
||||||
|
TypePackId forceTp
|
||||||
|
)
|
||||||
: arena(arena)
|
: arena(arena)
|
||||||
, builtinTypes(builtinTypes)
|
, builtinTypes(builtinTypes)
|
||||||
, types(types)
|
, types(types)
|
||||||
, packs(packs)
|
, packs(packs)
|
||||||
|
, forceTy(forceTy)
|
||||||
|
, forceTp(forceTp)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +125,7 @@ private:
|
||||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||||
if (auto it = types->find(ty); it != types->end())
|
if (auto it = types->find(ty); it != types->end())
|
||||||
return it->second;
|
return it->second;
|
||||||
else if (ty->persistent)
|
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||||
return ty;
|
return ty;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +135,7 @@ private:
|
||||||
tp = follow(tp);
|
tp = follow(tp);
|
||||||
if (auto it = packs->find(tp); it != packs->end())
|
if (auto it = packs->find(tp); it != packs->end())
|
||||||
return it->second;
|
return it->second;
|
||||||
else if (tp->persistent)
|
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||||
return tp;
|
return tp;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -148,7 +161,7 @@ public:
|
||||||
|
|
||||||
if (auto clone = find(ty))
|
if (auto clone = find(ty))
|
||||||
return *clone;
|
return *clone;
|
||||||
else if (ty->persistent)
|
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||||
return ty;
|
return ty;
|
||||||
|
|
||||||
TypeId target = arena->addType(ty->ty);
|
TypeId target = arena->addType(ty->ty);
|
||||||
|
@ -174,7 +187,7 @@ public:
|
||||||
|
|
||||||
if (auto clone = find(tp))
|
if (auto clone = find(tp))
|
||||||
return *clone;
|
return *clone;
|
||||||
else if (tp->persistent)
|
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||||
return tp;
|
return tp;
|
||||||
|
|
||||||
TypePackId target = arena->addTypePack(tp->ty);
|
TypePackId target = arena->addTypePack(tp->ty);
|
||||||
|
@ -458,21 +471,37 @@ private:
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
|
||||||
{
|
{
|
||||||
if (tp->persistent)
|
if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
|
||||||
return tp;
|
return tp;
|
||||||
|
|
||||||
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
TypeCloner cloner{
|
||||||
|
NotNull{&dest},
|
||||||
|
cloneState.builtinTypes,
|
||||||
|
NotNull{&cloneState.seenTypes},
|
||||||
|
NotNull{&cloneState.seenTypePacks},
|
||||||
|
nullptr,
|
||||||
|
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? tp : nullptr
|
||||||
|
};
|
||||||
|
|
||||||
return cloner.shallowClone(tp);
|
return cloner.shallowClone(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
|
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
|
||||||
{
|
{
|
||||||
if (typeId->persistent)
|
if (typeId->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
|
||||||
return typeId;
|
return typeId;
|
||||||
|
|
||||||
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
TypeCloner cloner{
|
||||||
|
NotNull{&dest},
|
||||||
|
cloneState.builtinTypes,
|
||||||
|
NotNull{&cloneState.seenTypes},
|
||||||
|
NotNull{&cloneState.seenTypePacks},
|
||||||
|
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? typeId : nullptr,
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
|
||||||
return cloner.shallowClone(typeId);
|
return cloner.shallowClone(typeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,7 +510,7 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
||||||
if (tp->persistent)
|
if (tp->persistent)
|
||||||
return tp;
|
return tp;
|
||||||
|
|
||||||
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr};
|
||||||
return cloner.clone(tp);
|
return cloner.clone(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,13 +519,13 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
|
||||||
if (typeId->persistent)
|
if (typeId->persistent)
|
||||||
return typeId;
|
return typeId;
|
||||||
|
|
||||||
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr};
|
||||||
return cloner.clone(typeId);
|
return cloner.clone(typeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
|
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
|
||||||
{
|
{
|
||||||
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
|
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr};
|
||||||
|
|
||||||
TypeFun copy = typeFun;
|
TypeFun copy = typeFun;
|
||||||
|
|
||||||
|
@ -521,4 +550,18 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState)
|
||||||
|
{
|
||||||
|
TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}, nullptr, nullptr};
|
||||||
|
|
||||||
|
Binding b;
|
||||||
|
b.deprecated = binding.deprecated;
|
||||||
|
b.deprecatedSuggestion = binding.deprecatedSuggestion;
|
||||||
|
b.documentationSymbol = binding.documentationSymbol;
|
||||||
|
b.location = binding.location;
|
||||||
|
b.typeId = cloner.clone(binding.typeId);
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -31,13 +31,14 @@
|
||||||
LUAU_FASTINT(LuauCheckRecursionLimit)
|
LUAU_FASTINT(LuauCheckRecursionLimit)
|
||||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
|
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
|
||||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||||
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
|
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
|
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
|
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(InferGlobalTypes)
|
LUAU_FASTFLAGVARIABLE(InferGlobalTypes)
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -1088,18 +1089,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
|
||||||
addConstraint(scope, value->location, NameConstraint{*firstValueType, var->name.value, /*synthetic*/ true});
|
addConstraint(scope, value->location, NameConstraint{*firstValueType, var->name.value, /*synthetic*/ true});
|
||||||
else if (const AstExprCall* call = value->as<AstExprCall>())
|
else if (const AstExprCall* call = value->as<AstExprCall>())
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypestateBuiltins2)
|
if (matchSetMetatable(*call))
|
||||||
{
|
addConstraint(scope, value->location, NameConstraint{*firstValueType, var->name.value, /*synthetic*/ true});
|
||||||
if (matchSetMetatable(*call))
|
|
||||||
addConstraint(scope, value->location, NameConstraint{*firstValueType, var->name.value, /*synthetic*/ true});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (const AstExprGlobal* global = call->func->as<AstExprGlobal>(); global && global->name == "setmetatable")
|
|
||||||
{
|
|
||||||
addConstraint(scope, value->location, NameConstraint{*firstValueType, var->name.value, /*synthetic*/ true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2068,7 +2059,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
||||||
return InferencePack{arena->addTypePack({resultTy}), {refinementArena.variadic(returnRefinements)}};
|
return InferencePack{arena->addTypePack({resultTy}), {refinementArena.variadic(returnRefinements)}};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauTypestateBuiltins2 && shouldTypestateForFirstArgument(*call) && call->args.size > 0 && isLValue(call->args.data[0]))
|
if (shouldTypestateForFirstArgument(*call) && call->args.size > 0 && isLValue(call->args.data[0]))
|
||||||
{
|
{
|
||||||
AstExpr* targetExpr = call->args.data[0];
|
AstExpr* targetExpr = call->args.data[0];
|
||||||
auto resultTy = arena->addType(BlockedType{});
|
auto resultTy = arena->addType(BlockedType{});
|
||||||
|
@ -2217,7 +2208,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
|
||||||
if (forceSingleton)
|
if (forceSingleton)
|
||||||
return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})};
|
return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})};
|
||||||
|
|
||||||
FreeType ft = FreeType{scope.get()};
|
FreeType ft =
|
||||||
|
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
|
||||||
ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}});
|
ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}});
|
||||||
ft.upperBound = builtinTypes->stringType;
|
ft.upperBound = builtinTypes->stringType;
|
||||||
const TypeId freeTy = arena->addType(ft);
|
const TypeId freeTy = arena->addType(ft);
|
||||||
|
@ -2231,7 +2223,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
|
||||||
if (forceSingleton)
|
if (forceSingleton)
|
||||||
return Inference{singletonType};
|
return Inference{singletonType};
|
||||||
|
|
||||||
FreeType ft = FreeType{scope.get()};
|
FreeType ft =
|
||||||
|
FFlag::LuauFreeTypesMustHaveBounds ? FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType} : FreeType{scope.get()};
|
||||||
ft.lowerBound = singletonType;
|
ft.lowerBound = singletonType;
|
||||||
ft.upperBound = builtinTypes->booleanType;
|
ft.upperBound = builtinTypes->booleanType;
|
||||||
const TypeId freeTy = arena->addType(ft);
|
const TypeId freeTy = arena->addType(ft);
|
||||||
|
@ -3427,6 +3420,12 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||||
}
|
}
|
||||||
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
|
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
{
|
||||||
|
if (unionAnnotation->types.size == 1)
|
||||||
|
return resolveType(scope, unionAnnotation->types.data[0], inTypeArguments);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<TypeId> parts;
|
std::vector<TypeId> parts;
|
||||||
for (AstType* part : unionAnnotation->types)
|
for (AstType* part : unionAnnotation->types)
|
||||||
{
|
{
|
||||||
|
@ -3437,6 +3436,12 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||||
}
|
}
|
||||||
else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>())
|
else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>())
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
{
|
||||||
|
if (intersectionAnnotation->types.size == 1)
|
||||||
|
return resolveType(scope, intersectionAnnotation->types.data[0], inTypeArguments);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<TypeId> parts;
|
std::vector<TypeId> parts;
|
||||||
for (AstType* part : intersectionAnnotation->types)
|
for (AstType* part : intersectionAnnotation->types)
|
||||||
{
|
{
|
||||||
|
@ -3445,6 +3450,10 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||||
|
|
||||||
result = arena->addType(IntersectionType{parts});
|
result = arena->addType(IntersectionType{parts});
|
||||||
}
|
}
|
||||||
|
else if (auto typeGroupAnnotation = ty->as<AstTypeGroup>())
|
||||||
|
{
|
||||||
|
result = resolveType(scope, typeGroupAnnotation->type, inTypeArguments);
|
||||||
|
}
|
||||||
else if (auto boolAnnotation = ty->as<AstTypeSingletonBool>())
|
else if (auto boolAnnotation = ty->as<AstTypeSingletonBool>())
|
||||||
{
|
{
|
||||||
if (boolAnnotation->value)
|
if (boolAnnotation->value)
|
||||||
|
|
|
@ -31,7 +31,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
|
||||||
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
|
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauRemoveNotAnyHack)
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
|
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
|
||||||
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
|
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
|
LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
|
||||||
|
@ -1161,22 +1160,8 @@ void ConstraintSolver::fillInDiscriminantTypes(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauRemoveNotAnyHack)
|
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
||||||
{
|
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
||||||
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
|
||||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We use `any` here because the discriminant type may be pointed at by both branches,
|
|
||||||
// where the discriminant type is not negated, and the other where it is negated, i.e.
|
|
||||||
// `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never`
|
|
||||||
// v.s.
|
|
||||||
// `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T`
|
|
||||||
//
|
|
||||||
// In practice, users cannot negate `any`, so this is an implementation detail we can always change.
|
|
||||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->anyType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1313,22 +1298,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauRemoveNotAnyHack)
|
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
||||||
{
|
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
||||||
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
|
||||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We use `any` here because the discriminant type may be pointed at by both branches,
|
|
||||||
// where the discriminant type is not negated, and the other where it is negated, i.e.
|
|
||||||
// `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never`
|
|
||||||
// v.s.
|
|
||||||
// `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T`
|
|
||||||
//
|
|
||||||
// In practice, users cannot negate `any`, so this is an implementation detail we can always change.
|
|
||||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->anyType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -879,7 +878,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
||||||
{
|
{
|
||||||
visitExpr(c->func);
|
visitExpr(c->func);
|
||||||
|
|
||||||
if (FFlag::LuauTypestateBuiltins2 && shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
|
if (shouldTypestateForFirstArgument(*c) && c->args.size > 1 && isLValue(*c->args.begin()))
|
||||||
{
|
{
|
||||||
AstExpr* firstArg = *c->args.begin();
|
AstExpr* firstArg = *c->args.begin();
|
||||||
|
|
||||||
|
@ -1170,6 +1169,8 @@ void DataFlowGraphBuilder::visitType(AstType* t)
|
||||||
return; // ok
|
return; // ok
|
||||||
else if (auto s = t->as<AstTypeSingletonString>())
|
else if (auto s = t->as<AstTypeSingletonString>())
|
||||||
return; // ok
|
return; // ok
|
||||||
|
else if (auto g = t->as<AstTypeGroup>())
|
||||||
|
return visitType(g->type);
|
||||||
else
|
else
|
||||||
handle->ice("Unknown AstType in DataFlowGraphBuilder::visitType");
|
handle->ice("Unknown AstType in DataFlowGraphBuilder::visitType");
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "Luau/Autocomplete.h"
|
#include "Luau/Autocomplete.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/EqSatSimplification.h"
|
#include "Luau/EqSatSimplification.h"
|
||||||
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/Parser.h"
|
#include "Luau/Parser.h"
|
||||||
#include "Luau/ParseOptions.h"
|
#include "Luau/ParseOptions.h"
|
||||||
#include "Luau/Module.h"
|
#include "Luau/Module.h"
|
||||||
|
@ -19,16 +20,21 @@
|
||||||
#include "Luau/Parser.h"
|
#include "Luau/Parser.h"
|
||||||
#include "Luau/ParseOptions.h"
|
#include "Luau/ParseOptions.h"
|
||||||
#include "Luau/Module.h"
|
#include "Luau/Module.h"
|
||||||
|
#include "Luau/Clone.h"
|
||||||
#include "AutocompleteCore.h"
|
#include "AutocompleteCore.h"
|
||||||
|
|
||||||
|
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
||||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||||
LUAU_FASTFLAG(LuauAllowFragmentParsing);
|
LUAU_FASTFLAG(LuauAllowFragmentParsing);
|
||||||
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes)
|
||||||
|
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
|
||||||
|
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -49,6 +55,96 @@ void copyModuleMap(Luau::DenseHashMap<K, V>& result, const Luau::DenseHashMap<K,
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
template<typename K, typename V>
|
||||||
|
void cloneModuleMap(TypeArena& destArena, CloneState& cloneState, const Luau::DenseHashMap<K, V>& source, Luau::DenseHashMap<K, V>& dest)
|
||||||
|
{
|
||||||
|
for (auto [k, v] : source)
|
||||||
|
{
|
||||||
|
dest[k] = Luau::clone(v, destArena, cloneState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MixedModeIncrementalTCDefFinder : public AstVisitor
|
||||||
|
{
|
||||||
|
bool visit(AstExprLocal* local) override
|
||||||
|
{
|
||||||
|
referencedLocalDefs.emplace_back(local->local, local);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(AstTypeTypeof* node) override
|
||||||
|
{
|
||||||
|
// We need to traverse typeof expressions because they may refer to locals that we need
|
||||||
|
// to populate the local environment for fragment typechecking. For example, `typeof(m)`
|
||||||
|
// requires that we find the local/global `m` and place it in the environment.
|
||||||
|
// The default behaviour here is to return false, and have individual visitors override
|
||||||
|
// the specific behaviour they need.
|
||||||
|
return FFlag::LuauMixedModeDefFinderTraversesTypeOf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ast defs is just a mapping from expr -> def in general
|
||||||
|
// will get built up by the dfg builder
|
||||||
|
|
||||||
|
// localDefs, we need to copy over
|
||||||
|
std::vector<std::pair<AstLocal*, AstExpr*>> referencedLocalDefs;
|
||||||
|
};
|
||||||
|
|
||||||
|
void cloneAndSquashScopes(
|
||||||
|
CloneState& cloneState,
|
||||||
|
const Scope* staleScope,
|
||||||
|
const ModulePtr& staleModule,
|
||||||
|
NotNull<TypeArena> destArena,
|
||||||
|
NotNull<DataFlowGraph> dfg,
|
||||||
|
AstStatBlock* program,
|
||||||
|
Scope* destScope
|
||||||
|
)
|
||||||
|
{
|
||||||
|
std::vector<const Scope*> scopes;
|
||||||
|
for (const Scope* current = staleScope; current; current = current->parent.get())
|
||||||
|
{
|
||||||
|
scopes.emplace_back(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// in reverse order (we need to clone the parents and override defs as we go down the list)
|
||||||
|
for (auto it = scopes.rbegin(); it != scopes.rend(); ++it)
|
||||||
|
{
|
||||||
|
const Scope* curr = *it;
|
||||||
|
// Clone the lvalue types
|
||||||
|
for (const auto& [def, ty] : curr->lvalueTypes)
|
||||||
|
destScope->lvalueTypes[def] = Luau::clone(ty, *destArena, cloneState);
|
||||||
|
// Clone the rvalueRefinements
|
||||||
|
for (const auto& [def, ty] : curr->rvalueRefinements)
|
||||||
|
destScope->rvalueRefinements[def] = Luau::clone(ty, *destArena, cloneState);
|
||||||
|
for (const auto& [n, m] : curr->importedTypeBindings)
|
||||||
|
{
|
||||||
|
std::unordered_map<Name, TypeFun> importedBindingTypes;
|
||||||
|
for (const auto& [v, tf] : m)
|
||||||
|
importedBindingTypes[v] = Luau::clone(tf, *destArena, cloneState);
|
||||||
|
destScope->importedTypeBindings[n] = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, clone up the bindings
|
||||||
|
for (const auto& [s, b] : curr->bindings)
|
||||||
|
{
|
||||||
|
destScope->bindings[s] = Luau::clone(b, *destArena, cloneState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The above code associates defs with TypeId's in the scope
|
||||||
|
// so that lookup to locals will succeed.
|
||||||
|
MixedModeIncrementalTCDefFinder finder;
|
||||||
|
program->visit(&finder);
|
||||||
|
std::vector<std::pair<AstLocal*, AstExpr*>> locals = std::move(finder.referencedLocalDefs);
|
||||||
|
for (auto [loc, expr] : locals)
|
||||||
|
{
|
||||||
|
if (std::optional<Binding> binding = staleScope->linearSearchForBinding(loc->name.value, true))
|
||||||
|
{
|
||||||
|
destScope->lvalueTypes[dfg->getDef(expr)] = Luau::clone(binding->typeId, *destArena, cloneState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<FrontendOptions> options)
|
static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<FrontendOptions> options)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauSolverV2 || !options)
|
if (FFlag::LuauSolverV2 || !options)
|
||||||
|
@ -265,13 +361,35 @@ std::optional<FragmentParseResult> parseFragment(
|
||||||
return fragmentResult;
|
return fragmentResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModulePtr cloneModule(CloneState& cloneState, const ModulePtr& source, std::unique_ptr<Allocator> alloc)
|
||||||
|
{
|
||||||
|
freeze(source->internalTypes);
|
||||||
|
freeze(source->interfaceTypes);
|
||||||
|
ModulePtr incremental = std::make_shared<Module>();
|
||||||
|
incremental->name = source->name;
|
||||||
|
incremental->humanReadableName = source->humanReadableName;
|
||||||
|
incremental->allocator = std::move(alloc);
|
||||||
|
// Clone types
|
||||||
|
cloneModuleMap(incremental->internalTypes, cloneState, source->astTypes, incremental->astTypes);
|
||||||
|
cloneModuleMap(incremental->internalTypes, cloneState, source->astTypePacks, incremental->astTypePacks);
|
||||||
|
cloneModuleMap(incremental->internalTypes, cloneState, source->astExpectedTypes, incremental->astExpectedTypes);
|
||||||
|
|
||||||
|
cloneModuleMap(incremental->internalTypes, cloneState, source->astOverloadResolvedTypes, incremental->astOverloadResolvedTypes);
|
||||||
|
|
||||||
|
cloneModuleMap(incremental->internalTypes, cloneState, source->astForInNextTypes, incremental->astForInNextTypes);
|
||||||
|
|
||||||
|
copyModuleMap(incremental->astScopes, source->astScopes);
|
||||||
|
|
||||||
|
return incremental;
|
||||||
|
}
|
||||||
|
|
||||||
ModulePtr copyModule(const ModulePtr& result, std::unique_ptr<Allocator> alloc)
|
ModulePtr copyModule(const ModulePtr& result, std::unique_ptr<Allocator> alloc)
|
||||||
{
|
{
|
||||||
freeze(result->internalTypes);
|
|
||||||
freeze(result->interfaceTypes);
|
|
||||||
ModulePtr incrementalModule = std::make_shared<Module>();
|
ModulePtr incrementalModule = std::make_shared<Module>();
|
||||||
incrementalModule->name = result->name;
|
incrementalModule->name = result->name;
|
||||||
incrementalModule->humanReadableName = result->humanReadableName;
|
incrementalModule->humanReadableName = "Incremental$" + result->humanReadableName;
|
||||||
|
incrementalModule->internalTypes.owningModule = incrementalModule.get();
|
||||||
|
incrementalModule->interfaceTypes.owningModule = incrementalModule.get();
|
||||||
incrementalModule->allocator = std::move(alloc);
|
incrementalModule->allocator = std::move(alloc);
|
||||||
// Don't need to keep this alive (it's already on the source module)
|
// Don't need to keep this alive (it's already on the source module)
|
||||||
copyModuleVec(incrementalModule->scopes, result->scopes);
|
copyModuleVec(incrementalModule->scopes, result->scopes);
|
||||||
|
@ -290,21 +408,6 @@ ModulePtr copyModule(const ModulePtr& result, std::unique_ptr<Allocator> alloc)
|
||||||
return incrementalModule;
|
return incrementalModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MixedModeIncrementalTCDefFinder : public AstVisitor
|
|
||||||
{
|
|
||||||
bool visit(AstExprLocal* local) override
|
|
||||||
{
|
|
||||||
referencedLocalDefs.push_back({local->local, local});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ast defs is just a mapping from expr -> def in general
|
|
||||||
// will get built up by the dfg builder
|
|
||||||
|
|
||||||
// localDefs, we need to copy over
|
|
||||||
std::vector<std::pair<AstLocal*, AstExpr*>> referencedLocalDefs;
|
|
||||||
};
|
|
||||||
|
|
||||||
void mixedModeCompatibility(
|
void mixedModeCompatibility(
|
||||||
const ScopePtr& bottomScopeStale,
|
const ScopePtr& bottomScopeStale,
|
||||||
const ScopePtr& myFakeScope,
|
const ScopePtr& myFakeScope,
|
||||||
|
@ -343,7 +446,9 @@ FragmentTypeCheckResult typecheckFragment_(
|
||||||
{
|
{
|
||||||
freeze(stale->internalTypes);
|
freeze(stale->internalTypes);
|
||||||
freeze(stale->interfaceTypes);
|
freeze(stale->interfaceTypes);
|
||||||
ModulePtr incrementalModule = copyModule(stale, std::move(astAllocator));
|
CloneState cloneState{frontend.builtinTypes};
|
||||||
|
ModulePtr incrementalModule =
|
||||||
|
FFlag::LuauCloneIncrementalModule ? cloneModule(cloneState, stale, std::move(astAllocator)) : copyModule(stale, std::move(astAllocator));
|
||||||
incrementalModule->checkedInNewSolver = true;
|
incrementalModule->checkedInNewSolver = true;
|
||||||
unfreeze(incrementalModule->internalTypes);
|
unfreeze(incrementalModule->internalTypes);
|
||||||
unfreeze(incrementalModule->interfaceTypes);
|
unfreeze(incrementalModule->interfaceTypes);
|
||||||
|
@ -391,25 +496,34 @@ FragmentTypeCheckResult typecheckFragment_(
|
||||||
NotNull{&dfg},
|
NotNull{&dfg},
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
std::shared_ptr<Scope> freshChildOfNearestScope = nullptr;
|
||||||
|
if (FFlag::LuauCloneIncrementalModule)
|
||||||
|
{
|
||||||
|
freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
|
||||||
|
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||||
|
cg.rootScope = freshChildOfNearestScope.get();
|
||||||
|
|
||||||
cg.rootScope = stale->getModuleScope().get();
|
cloneAndSquashScopes(
|
||||||
// Any additions to the scope must occur in a fresh scope
|
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||||
auto freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
|
);
|
||||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||||
|
}
|
||||||
// Update freshChildOfNearestScope with the appropriate lvalueTypes
|
else
|
||||||
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
|
{
|
||||||
|
// Any additions to the scope must occur in a fresh scope
|
||||||
// closest Scope -> children = { ...., freshChildOfNearestScope}
|
cg.rootScope = stale->getModuleScope().get();
|
||||||
// We need to trim nearestChild from the scope hierarcy
|
freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
|
||||||
closestScope->children.push_back(NotNull{freshChildOfNearestScope.get()});
|
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||||
// Visit just the root - we know the scope it should be in
|
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
|
||||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
// closest Scope -> children = { ...., freshChildOfNearestScope}
|
||||||
// Trim nearestChild from the closestScope
|
// We need to trim nearestChild from the scope hierarcy
|
||||||
Scope* back = closestScope->children.back().get();
|
closestScope->children.emplace_back(freshChildOfNearestScope.get());
|
||||||
LUAU_ASSERT(back == freshChildOfNearestScope.get());
|
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||||
closestScope->children.pop_back();
|
// Trim nearestChild from the closestScope
|
||||||
|
Scope* back = closestScope->children.back().get();
|
||||||
|
LUAU_ASSERT(back == freshChildOfNearestScope.get());
|
||||||
|
closestScope->children.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize the constraint solver and run it
|
/// Initialize the constraint solver and run it
|
||||||
ConstraintSolver cs{
|
ConstraintSolver cs{
|
||||||
|
@ -458,6 +572,13 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
||||||
std::optional<Position> fragmentEndPosition
|
std::optional<Position> fragmentEndPosition
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||||
|
{
|
||||||
|
if (!frontend.allModuleDependenciesValid(moduleName, opts && opts->forAutocomplete))
|
||||||
|
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||||
|
}
|
||||||
|
|
||||||
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
||||||
if (!sourceModule)
|
if (!sourceModule)
|
||||||
{
|
{
|
||||||
|
@ -473,6 +594,14 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauIncrementalAutocompleteBugfixes && FFlag::LuauReferenceAllocatorInNewSolver)
|
||||||
|
{
|
||||||
|
if (sourceModule->allocator.get() != module->allocator.get())
|
||||||
|
{
|
||||||
|
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto tryParse = parseFragment(*sourceModule, src, cursorPos, fragmentEndPosition);
|
auto tryParse = parseFragment(*sourceModule, src, cursorPos, fragmentEndPosition);
|
||||||
|
|
||||||
if (!tryParse)
|
if (!tryParse)
|
||||||
|
|
|
@ -47,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
|
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
|
||||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauBetterReverseDependencyTracking)
|
||||||
|
|
||||||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
LUAU_FASTFLAG(StudioReportLuauAny2)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule)
|
LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule)
|
||||||
|
|
||||||
|
@ -820,6 +822,16 @@ bool Frontend::parseGraph(
|
||||||
topseen = Permanent;
|
topseen = Permanent;
|
||||||
|
|
||||||
buildQueue.push_back(top->name);
|
buildQueue.push_back(top->name);
|
||||||
|
|
||||||
|
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||||
|
{
|
||||||
|
// at this point we know all valid dependencies are processed into SourceNodes
|
||||||
|
for (const ModuleName& dep : top->requireSet)
|
||||||
|
{
|
||||||
|
if (auto it = sourceNodes.find(dep); it != sourceNodes.end())
|
||||||
|
it->second->dependents.insert(top->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1107,15 +1119,49 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
|
||||||
if (item.exception)
|
if (item.exception)
|
||||||
std::rethrow_exception(item.exception);
|
std::rethrow_exception(item.exception);
|
||||||
|
|
||||||
if (item.options.forAutocomplete)
|
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||||
{
|
{
|
||||||
moduleResolverForAutocomplete.setModule(item.name, item.module);
|
bool replacedModule = false;
|
||||||
item.sourceNode->dirtyModuleForAutocomplete = false;
|
if (item.options.forAutocomplete)
|
||||||
|
{
|
||||||
|
replacedModule = moduleResolverForAutocomplete.setModule(item.name, item.module);
|
||||||
|
item.sourceNode->dirtyModuleForAutocomplete = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
replacedModule = moduleResolver.setModule(item.name, item.module);
|
||||||
|
item.sourceNode->dirtyModule = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replacedModule)
|
||||||
|
{
|
||||||
|
LUAU_TIMETRACE_SCOPE("Frontend::invalidateDependentModules", "Frontend");
|
||||||
|
LUAU_TIMETRACE_ARGUMENT("name", item.name.c_str());
|
||||||
|
traverseDependents(
|
||||||
|
item.name,
|
||||||
|
[forAutocomplete = item.options.forAutocomplete](SourceNode& sourceNode)
|
||||||
|
{
|
||||||
|
bool traverseSubtree = !sourceNode.hasInvalidModuleDependency(forAutocomplete);
|
||||||
|
sourceNode.setInvalidModuleDependency(true, forAutocomplete);
|
||||||
|
return traverseSubtree;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.sourceNode->setInvalidModuleDependency(false, item.options.forAutocomplete);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
moduleResolver.setModule(item.name, item.module);
|
if (item.options.forAutocomplete)
|
||||||
item.sourceNode->dirtyModule = false;
|
{
|
||||||
|
moduleResolverForAutocomplete.setModule(item.name, item.module);
|
||||||
|
item.sourceNode->dirtyModuleForAutocomplete = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
moduleResolver.setModule(item.name, item.module);
|
||||||
|
item.sourceNode->dirtyModule = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.timeCheck += item.stats.timeCheck;
|
stats.timeCheck += item.stats.timeCheck;
|
||||||
|
@ -1152,6 +1198,13 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Frontend::allModuleDependenciesValid(const ModuleName& name, bool forAutocomplete) const
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauBetterReverseDependencyTracking);
|
||||||
|
auto it = sourceNodes.find(name);
|
||||||
|
return it != sourceNodes.end() && !it->second->hasInvalidModuleDependency(forAutocomplete);
|
||||||
|
}
|
||||||
|
|
||||||
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
||||||
{
|
{
|
||||||
auto it = sourceNodes.find(name);
|
auto it = sourceNodes.find(name);
|
||||||
|
@ -1166,16 +1219,80 @@ bool Frontend::isDirty(const ModuleName& name, bool forAutocomplete) const
|
||||||
*/
|
*/
|
||||||
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
|
void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* markedDirty)
|
||||||
{
|
{
|
||||||
|
LUAU_TIMETRACE_SCOPE("Frontend::markDirty", "Frontend");
|
||||||
|
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
||||||
|
|
||||||
|
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||||
|
{
|
||||||
|
traverseDependents(
|
||||||
|
name,
|
||||||
|
[markedDirty](SourceNode& sourceNode)
|
||||||
|
{
|
||||||
|
if (markedDirty)
|
||||||
|
markedDirty->push_back(sourceNode.name);
|
||||||
|
|
||||||
|
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
sourceNode.dirtySourceModule = true;
|
||||||
|
sourceNode.dirtyModule = true;
|
||||||
|
sourceNode.dirtyModuleForAutocomplete = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (sourceNodes.count(name) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
||||||
|
for (const auto& module : sourceNodes)
|
||||||
|
{
|
||||||
|
for (const auto& dep : module.second->requireSet)
|
||||||
|
reverseDeps[dep].push_back(module.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModuleName> queue{name};
|
||||||
|
|
||||||
|
while (!queue.empty())
|
||||||
|
{
|
||||||
|
ModuleName next = std::move(queue.back());
|
||||||
|
queue.pop_back();
|
||||||
|
|
||||||
|
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
||||||
|
SourceNode& sourceNode = *sourceNodes[next];
|
||||||
|
|
||||||
|
if (markedDirty)
|
||||||
|
markedDirty->push_back(next);
|
||||||
|
|
||||||
|
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sourceNode.dirtySourceModule = true;
|
||||||
|
sourceNode.dirtyModule = true;
|
||||||
|
sourceNode.dirtyModuleForAutocomplete = true;
|
||||||
|
|
||||||
|
if (0 == reverseDeps.count(next))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sourceModules.erase(next);
|
||||||
|
|
||||||
|
const std::vector<ModuleName>& dependents = reverseDeps[next];
|
||||||
|
queue.insert(queue.end(), dependents.begin(), dependents.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Frontend::traverseDependents(const ModuleName& name, std::function<bool(SourceNode&)> processSubtree)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauBetterReverseDependencyTracking);
|
||||||
|
LUAU_TIMETRACE_SCOPE("Frontend::traverseDependents", "Frontend");
|
||||||
|
|
||||||
if (sourceNodes.count(name) == 0)
|
if (sourceNodes.count(name) == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::unordered_map<ModuleName, std::vector<ModuleName>> reverseDeps;
|
|
||||||
for (const auto& module : sourceNodes)
|
|
||||||
{
|
|
||||||
for (const auto& dep : module.second->requireSet)
|
|
||||||
reverseDeps[dep].push_back(module.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ModuleName> queue{name};
|
std::vector<ModuleName> queue{name};
|
||||||
|
|
||||||
while (!queue.empty())
|
while (!queue.empty())
|
||||||
|
@ -1186,22 +1303,10 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
|
||||||
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
||||||
SourceNode& sourceNode = *sourceNodes[next];
|
SourceNode& sourceNode = *sourceNodes[next];
|
||||||
|
|
||||||
if (markedDirty)
|
if (!processSubtree(sourceNode))
|
||||||
markedDirty->push_back(next);
|
|
||||||
|
|
||||||
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
sourceNode.dirtySourceModule = true;
|
const Set<ModuleName>& dependents = sourceNode.dependents;
|
||||||
sourceNode.dirtyModule = true;
|
|
||||||
sourceNode.dirtyModuleForAutocomplete = true;
|
|
||||||
|
|
||||||
if (0 == reverseDeps.count(next))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
sourceModules.erase(next);
|
|
||||||
|
|
||||||
const std::vector<ModuleName>& dependents = reverseDeps[next];
|
|
||||||
queue.insert(queue.end(), dependents.begin(), dependents.end());
|
queue.insert(queue.end(), dependents.begin(), dependents.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1643,6 +1748,17 @@ std::pair<SourceNode*, SourceModule*> Frontend::getSourceNode(const ModuleName&
|
||||||
|
|
||||||
sourceNode->name = sourceModule->name;
|
sourceNode->name = sourceModule->name;
|
||||||
sourceNode->humanReadableName = sourceModule->humanReadableName;
|
sourceNode->humanReadableName = sourceModule->humanReadableName;
|
||||||
|
|
||||||
|
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||||
|
{
|
||||||
|
// clear all prior dependents. we will re-add them after parsing the rest of the graph
|
||||||
|
for (const auto& [moduleName, _] : sourceNode->requireLocations)
|
||||||
|
{
|
||||||
|
if (auto depIt = sourceNodes.find(moduleName); depIt != sourceNodes.end())
|
||||||
|
depIt->second->dependents.erase(sourceNode->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceNode->requireSet.clear();
|
sourceNode->requireSet.clear();
|
||||||
sourceNode->requireLocations.clear();
|
sourceNode->requireLocations.clear();
|
||||||
sourceNode->dirtySourceModule = false;
|
sourceNode->dirtySourceModule = false;
|
||||||
|
@ -1764,11 +1880,21 @@ std::string FrontendModuleResolver::getHumanReadableModuleName(const ModuleName&
|
||||||
return frontend->fileResolver->getHumanReadableModuleName(moduleName);
|
return frontend->fileResolver->getHumanReadableModuleName(moduleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr module)
|
bool FrontendModuleResolver::setModule(const ModuleName& moduleName, ModulePtr module)
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(moduleMutex);
|
std::scoped_lock lock(moduleMutex);
|
||||||
|
|
||||||
modules[moduleName] = std::move(module);
|
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||||
|
{
|
||||||
|
bool replaced = modules.count(moduleName) > 0;
|
||||||
|
modules[moduleName] = std::move(module);
|
||||||
|
return replaced;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
modules[moduleName] = std::move(module);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FrontendModuleResolver::clearModules()
|
void FrontendModuleResolver::clearModules()
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -163,7 +164,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return addType(FreeType{scope, level});
|
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, scope, level) : addType(FreeType{scope, level});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCountSelfCallsNonstrict)
|
LUAU_FASTFLAGVARIABLE(LuauCountSelfCallsNonstrict)
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -211,7 +212,7 @@ struct NonStrictTypeChecker
|
||||||
return *fst;
|
return *fst;
|
||||||
else if (auto ftp = get<FreeTypePack>(pack))
|
else if (auto ftp = get<FreeTypePack>(pack))
|
||||||
{
|
{
|
||||||
TypeId result = arena->addType(FreeType{ftp->scope});
|
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, ftp->scope) : arena->addType(FreeType{ftp->scope});
|
||||||
TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope});
|
TypePackId freeTail = arena->addTypePack(FreeTypePack{ftp->scope});
|
||||||
|
|
||||||
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
|
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
|
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
|
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationTracksCyclicPairsThroughInhabitance)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFixNormalizedIntersectionOfNegatedClass)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -2284,9 +2284,24 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
||||||
else if (isSubclass(there, hereTy))
|
else if (isSubclass(there, hereTy))
|
||||||
{
|
{
|
||||||
TypeIds negations = std::move(hereNegations);
|
TypeIds negations = std::move(hereNegations);
|
||||||
|
bool emptyIntersectWithNegation = false;
|
||||||
|
|
||||||
for (auto nIt = negations.begin(); nIt != negations.end();)
|
for (auto nIt = negations.begin(); nIt != negations.end();)
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauFixNormalizedIntersectionOfNegatedClass && isSubclass(there, *nIt))
|
||||||
|
{
|
||||||
|
// Hitting this block means that the incoming class is a
|
||||||
|
// subclass of this type, _and_ one of its negations is a
|
||||||
|
// superclass of this type, e.g.:
|
||||||
|
//
|
||||||
|
// Dog & ~Animal
|
||||||
|
//
|
||||||
|
// Clearly this intersects to never, so we mark this class as
|
||||||
|
// being removed from the normalized class type.
|
||||||
|
emptyIntersectWithNegation = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isSubclass(*nIt, there))
|
if (!isSubclass(*nIt, there))
|
||||||
{
|
{
|
||||||
nIt = negations.erase(nIt);
|
nIt = negations.erase(nIt);
|
||||||
|
@ -2299,7 +2314,8 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
||||||
|
|
||||||
it = heres.ordering.erase(it);
|
it = heres.ordering.erase(it);
|
||||||
heres.classes.erase(hereTy);
|
heres.classes.erase(hereTy);
|
||||||
heres.pushPair(there, std::move(negations));
|
if (!emptyIntersectWithNegation)
|
||||||
|
heres.pushPair(there, std::move(negations));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// If the incoming class is a superclass of the current class, we don't
|
// If the incoming class is a superclass of the current class, we don't
|
||||||
|
@ -2584,11 +2600,31 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||||
{
|
{
|
||||||
if (tprop.readTy.has_value())
|
if (tprop.readTy.has_value())
|
||||||
{
|
{
|
||||||
// if the intersection of the read types of a property is uninhabited, the whole table is `never`.
|
if (FFlag::LuauFixInfiniteRecursionInNormalization)
|
||||||
// We've seen these table prop elements before and we're about to ask if their intersection
|
|
||||||
// is inhabited
|
|
||||||
if (FFlag::LuauNormalizationTracksCyclicPairsThroughInhabitance)
|
|
||||||
{
|
{
|
||||||
|
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
|
||||||
|
|
||||||
|
// If any property is going to get mapped to `never`, we can just call the entire table `never`.
|
||||||
|
// Since this check is syntactic, we may sometimes miss simplifying tables with complex uninhabited properties.
|
||||||
|
// Prior versions of this code attempted to do this semantically using the normalization machinery, but this
|
||||||
|
// mistakenly causes infinite loops when giving more complex recursive table types. As it stands, this approach
|
||||||
|
// will continue to scale as simplification is improved, but we may wish to reintroduce the semantic approach
|
||||||
|
// once we have revisited the usage of seen sets systematically (and possibly with some additional guarding to recognize
|
||||||
|
// when types are infinitely-recursive with non-pointer identical instances of them, or some guard to prevent that
|
||||||
|
// construction altogether). See also: `gh1632_no_infinite_recursion_in_normalization`
|
||||||
|
if (get<NeverType>(ty))
|
||||||
|
return {builtinTypes->neverType};
|
||||||
|
|
||||||
|
prop.readTy = ty;
|
||||||
|
hereSubThere &= (ty == hprop.readTy);
|
||||||
|
thereSubHere &= (ty == tprop.readTy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if the intersection of the read types of a property is uninhabited, the whole table is `never`.
|
||||||
|
// We've seen these table prop elements before and we're about to ask if their intersection
|
||||||
|
// is inhabited
|
||||||
|
|
||||||
auto pair1 = std::pair{*hprop.readTy, *tprop.readTy};
|
auto pair1 = std::pair{*hprop.readTy, *tprop.readTy};
|
||||||
auto pair2 = std::pair{*tprop.readTy, *hprop.readTy};
|
auto pair2 = std::pair{*tprop.readTy, *hprop.readTy};
|
||||||
if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2))
|
if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2))
|
||||||
|
@ -2603,6 +2639,8 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||||
seenTablePropPairs.insert(pair2);
|
seenTablePropPairs.insert(pair2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME(ariel): this is being added in a flag removal, so not changing the semantics here, but worth noting that this
|
||||||
|
// fresh `seenSet` is definitely a bug. we already have `seenSet` from the parameter that _should_ have been used here.
|
||||||
Set<TypeId> seenSet{nullptr};
|
Set<TypeId> seenSet{nullptr};
|
||||||
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy, seenTablePropPairs, seenSet);
|
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy, seenTablePropPairs, seenSet);
|
||||||
|
|
||||||
|
@ -2616,34 +2654,6 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||||
hereSubThere &= (ty == hprop.readTy);
|
hereSubThere &= (ty == hprop.readTy);
|
||||||
thereSubHere &= (ty == tprop.readTy);
|
thereSubHere &= (ty == tprop.readTy);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
if (seenSet.contains(*hprop.readTy) && seenSet.contains(*tprop.readTy))
|
|
||||||
{
|
|
||||||
seenSet.erase(*hprop.readTy);
|
|
||||||
seenSet.erase(*tprop.readTy);
|
|
||||||
return {builtinTypes->neverType};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
seenSet.insert(*hprop.readTy);
|
|
||||||
seenSet.insert(*tprop.readTy);
|
|
||||||
}
|
|
||||||
|
|
||||||
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy);
|
|
||||||
|
|
||||||
seenSet.erase(*hprop.readTy);
|
|
||||||
seenSet.erase(*tprop.readTy);
|
|
||||||
|
|
||||||
if (NormalizationResult::True != res)
|
|
||||||
return {builtinTypes->neverType};
|
|
||||||
|
|
||||||
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
|
|
||||||
prop.readTy = ty;
|
|
||||||
hereSubThere &= (ty == hprop.readTy);
|
|
||||||
thereSubHere &= (ty == tprop.readTy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,16 +31,16 @@ struct TypeSimplifier
|
||||||
|
|
||||||
int recursionDepth = 0;
|
int recursionDepth = 0;
|
||||||
|
|
||||||
TypeId mkNegation(TypeId ty);
|
TypeId mkNegation(TypeId ty) const;
|
||||||
|
|
||||||
TypeId intersectFromParts(std::set<TypeId> parts);
|
TypeId intersectFromParts(std::set<TypeId> parts);
|
||||||
|
|
||||||
TypeId intersectUnionWithType(TypeId unionTy, TypeId right);
|
TypeId intersectUnionWithType(TypeId left, TypeId right);
|
||||||
TypeId intersectUnions(TypeId left, TypeId right);
|
TypeId intersectUnions(TypeId left, TypeId right);
|
||||||
TypeId intersectNegatedUnion(TypeId unionTy, TypeId right);
|
TypeId intersectNegatedUnion(TypeId left, TypeId right);
|
||||||
|
|
||||||
TypeId intersectTypeWithNegation(TypeId a, TypeId b);
|
TypeId intersectTypeWithNegation(TypeId left, TypeId right);
|
||||||
TypeId intersectNegations(TypeId a, TypeId b);
|
TypeId intersectNegations(TypeId left, TypeId right);
|
||||||
|
|
||||||
TypeId intersectIntersectionWithType(TypeId left, TypeId right);
|
TypeId intersectIntersectionWithType(TypeId left, TypeId right);
|
||||||
|
|
||||||
|
@ -48,8 +48,8 @@ struct TypeSimplifier
|
||||||
// unions, intersections, or negations.
|
// unions, intersections, or negations.
|
||||||
std::optional<TypeId> basicIntersect(TypeId left, TypeId right);
|
std::optional<TypeId> basicIntersect(TypeId left, TypeId right);
|
||||||
|
|
||||||
TypeId intersect(TypeId ty, TypeId discriminant);
|
TypeId intersect(TypeId left, TypeId right);
|
||||||
TypeId union_(TypeId ty, TypeId discriminant);
|
TypeId union_(TypeId left, TypeId right);
|
||||||
|
|
||||||
TypeId simplify(TypeId ty);
|
TypeId simplify(TypeId ty);
|
||||||
TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen);
|
TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen);
|
||||||
|
@ -573,7 +573,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
return relate(left, right, seen);
|
return relate(left, right, seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId TypeSimplifier::mkNegation(TypeId ty)
|
TypeId TypeSimplifier::mkNegation(TypeId ty) const
|
||||||
{
|
{
|
||||||
TypeId result = nullptr;
|
TypeId result = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauRetrySubtypingWithoutHiddenPack)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -1474,7 +1473,7 @@ SubtypingResult Subtyping::isCovariantWith(
|
||||||
|
|
||||||
// If subtyping failed in the argument packs, we should check if there's a hidden variadic tail and try ignoring it.
|
// If subtyping failed in the argument packs, we should check if there's a hidden variadic tail and try ignoring it.
|
||||||
// This might cause subtyping correctly because the sub type here may not have a hidden variadic tail or equivalent.
|
// This might cause subtyping correctly because the sub type here may not have a hidden variadic tail or equivalent.
|
||||||
if (FFlag::LuauRetrySubtypingWithoutHiddenPack && !result.isSubtype)
|
if (!result.isSubtype)
|
||||||
{
|
{
|
||||||
auto [arguments, tail] = flatten(superFunction->argTypes);
|
auto [arguments, tail] = flatten(superFunction->argTypes);
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
||||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -478,24 +479,12 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FreeType::FreeType(TypeLevel level)
|
// New constructors
|
||||||
|
FreeType::FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound)
|
||||||
: index(Unifiable::freshIndex())
|
: index(Unifiable::freshIndex())
|
||||||
, level(level)
|
, level(level)
|
||||||
, scope(nullptr)
|
, lowerBound(lowerBound)
|
||||||
{
|
, upperBound(upperBound)
|
||||||
}
|
|
||||||
|
|
||||||
FreeType::FreeType(Scope* scope)
|
|
||||||
: index(Unifiable::freshIndex())
|
|
||||||
, level{}
|
|
||||||
, scope(scope)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
FreeType::FreeType(Scope* scope, TypeLevel level)
|
|
||||||
: index(Unifiable::freshIndex())
|
|
||||||
, level(level)
|
|
||||||
, scope(scope)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,6 +496,40 @@ FreeType::FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FreeType::FreeType(Scope* scope, TypeLevel level, TypeId lowerBound, TypeId upperBound)
|
||||||
|
: index(Unifiable::freshIndex())
|
||||||
|
, level(level)
|
||||||
|
, scope(scope)
|
||||||
|
, lowerBound(lowerBound)
|
||||||
|
, upperBound(upperBound)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old constructors
|
||||||
|
FreeType::FreeType(TypeLevel level)
|
||||||
|
: index(Unifiable::freshIndex())
|
||||||
|
, level(level)
|
||||||
|
, scope(nullptr)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeType::FreeType(Scope* scope)
|
||||||
|
: index(Unifiable::freshIndex())
|
||||||
|
, level{}
|
||||||
|
, scope(scope)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeType::FreeType(Scope* scope, TypeLevel level)
|
||||||
|
: index(Unifiable::freshIndex())
|
||||||
|
, level(level)
|
||||||
|
, scope(scope)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!FFlag::LuauFreeTypesMustHaveBounds);
|
||||||
|
}
|
||||||
|
|
||||||
GenericType::GenericType()
|
GenericType::GenericType()
|
||||||
: index(Unifiable::freshIndex())
|
: index(Unifiable::freshIndex())
|
||||||
, name("g" + std::to_string(index))
|
, name("g" + std::to_string(index))
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena);
|
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena);
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -22,7 +23,34 @@ TypeId TypeArena::addTV(Type&& tv)
|
||||||
return allocated;
|
return allocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId TypeArena::freshType(TypeLevel level)
|
TypeId TypeArena::freshType(NotNull<BuiltinTypes> builtins, TypeLevel level)
|
||||||
|
{
|
||||||
|
TypeId allocated = types.allocate(FreeType{level, builtins->neverType, builtins->unknownType});
|
||||||
|
|
||||||
|
asMutable(allocated)->owningArena = this;
|
||||||
|
|
||||||
|
return allocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId TypeArena::freshType(NotNull<BuiltinTypes> builtins, Scope* scope)
|
||||||
|
{
|
||||||
|
TypeId allocated = types.allocate(FreeType{scope, builtins->neverType, builtins->unknownType});
|
||||||
|
|
||||||
|
asMutable(allocated)->owningArena = this;
|
||||||
|
|
||||||
|
return allocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId TypeArena::freshType(NotNull<BuiltinTypes> builtins, Scope* scope, TypeLevel level)
|
||||||
|
{
|
||||||
|
TypeId allocated = types.allocate(FreeType{scope, level, builtins->neverType, builtins->unknownType});
|
||||||
|
|
||||||
|
asMutable(allocated)->owningArena = this;
|
||||||
|
|
||||||
|
return allocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId TypeArena::freshType_DEPRECATED(TypeLevel level)
|
||||||
{
|
{
|
||||||
TypeId allocated = types.allocate(FreeType{level});
|
TypeId allocated = types.allocate(FreeType{level});
|
||||||
|
|
||||||
|
@ -31,7 +59,7 @@ TypeId TypeArena::freshType(TypeLevel level)
|
||||||
return allocated;
|
return allocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId TypeArena::freshType(Scope* scope)
|
TypeId TypeArena::freshType_DEPRECATED(Scope* scope)
|
||||||
{
|
{
|
||||||
TypeId allocated = types.allocate(FreeType{scope});
|
TypeId allocated = types.allocate(FreeType{scope});
|
||||||
|
|
||||||
|
@ -40,7 +68,7 @@ TypeId TypeArena::freshType(Scope* scope)
|
||||||
return allocated;
|
return allocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId TypeArena::freshType(Scope* scope, TypeLevel level)
|
TypeId TypeArena::freshType_DEPRECATED(Scope* scope, TypeLevel level)
|
||||||
{
|
{
|
||||||
TypeId allocated = types.allocate(FreeType{scope, level});
|
TypeId allocated = types.allocate(FreeType{scope, level});
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||||
|
|
||||||
LUAU_FASTFLAG(InferGlobalTypes)
|
LUAU_FASTFLAG(InferGlobalTypes)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTableKeysAreRValues)
|
LUAU_FASTFLAGVARIABLE(LuauTableKeysAreRValues)
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -2105,7 +2106,10 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
expectedRets = module->internalTypes.addTypePack({module->internalTypes.freshType(scope, TypeLevel{})});
|
expectedRets = module->internalTypes.addTypePack(
|
||||||
|
{FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, scope, TypeLevel{})
|
||||||
|
: module->internalTypes.freshType_DEPRECATED(scope, TypeLevel{})}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets));
|
TypeId expectedTy = module->internalTypes.addType(FunctionType(expectedArgs, expectedRets));
|
||||||
|
@ -2357,7 +2361,8 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
|
||||||
return *fst;
|
return *fst;
|
||||||
else if (auto ftp = get<FreeTypePack>(pack))
|
else if (auto ftp = get<FreeTypePack>(pack))
|
||||||
{
|
{
|
||||||
TypeId result = module->internalTypes.addType(FreeType{ftp->scope});
|
TypeId result = FFlag::LuauFreeTypesMustHaveBounds ? module->internalTypes.freshType(builtinTypes, ftp->scope)
|
||||||
|
: module->internalTypes.addType(FreeType{ftp->scope});
|
||||||
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
|
TypePackId freeTail = module->internalTypes.addTypePack(FreeTypePack{ftp->scope});
|
||||||
|
|
||||||
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
|
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
|
||||||
|
@ -2419,6 +2424,8 @@ void TypeChecker2::visit(AstType* ty)
|
||||||
return visit(t);
|
return visit(t);
|
||||||
else if (auto t = ty->as<AstTypeIntersection>())
|
else if (auto t = ty->as<AstTypeIntersection>())
|
||||||
return visit(t);
|
return visit(t);
|
||||||
|
else if (auto t = ty->as<AstTypeGroup>())
|
||||||
|
return visit(t->type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeChecker2::visit(AstTypeReference* ty)
|
void TypeChecker2::visit(AstTypeReference* ty)
|
||||||
|
|
|
@ -47,7 +47,9 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
|
||||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||||
LUAU_FASTFLAG(LuauRemoveNotAnyHack)
|
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauDoNotGeneralizeInTypeFunctions)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -825,7 +827,7 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
||||||
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
||||||
|
|
||||||
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy, /* avoidSealingTables */ true);
|
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy, /* avoidSealingTables */ true);
|
||||||
if (!maybeGeneralized)
|
if (!maybeGeneralized)
|
||||||
|
@ -917,7 +919,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
||||||
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
||||||
|
|
||||||
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy);
|
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy);
|
||||||
if (!maybeGeneralized)
|
if (!maybeGeneralized)
|
||||||
|
@ -1030,7 +1032,7 @@ std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunc
|
||||||
AstStat* stmtArray[] = {&stmtReturn};
|
AstStat* stmtArray[] = {&stmtReturn};
|
||||||
AstArray<AstStat*> stmts{stmtArray, 1};
|
AstArray<AstStat*> stmts{stmtArray, 1};
|
||||||
AstStatBlock exec{Location{}, stmts};
|
AstStatBlock exec{Location{}, stmts};
|
||||||
ParseResult parseResult{&exec, 1};
|
ParseResult parseResult{&exec, 1, {}, {}, {}, CstNodeMap{nullptr}};
|
||||||
|
|
||||||
BytecodeBuilder builder;
|
BytecodeBuilder builder;
|
||||||
try
|
try
|
||||||
|
@ -1160,7 +1162,7 @@ TypeFunctionReductionResult<TypeId> numericBinopTypeFunction(
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||||
|
@ -1397,7 +1399,7 @@ TypeFunctionReductionResult<TypeId> concatTypeFunction(
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||||
|
@ -1512,7 +1514,7 @@ TypeFunctionReductionResult<TypeId> andTypeFunction(
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||||
|
@ -1567,7 +1569,7 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||||
|
@ -1653,7 +1655,7 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
|
||||||
rhsTy = follow(rhsTy);
|
rhsTy = follow(rhsTy);
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||||
|
@ -1791,7 +1793,7 @@ TypeFunctionReductionResult<TypeId> eqTypeFunction(
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||||
|
@ -1936,7 +1938,7 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
||||||
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
|
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
|
||||||
{
|
{
|
||||||
std::vector<TypeId> toBlock;
|
std::vector<TypeId> toBlock;
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> targetMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, target);
|
std::optional<TypeId> targetMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, target);
|
||||||
std::optional<TypeId> discriminantMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, discriminant);
|
std::optional<TypeId> discriminantMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, discriminant);
|
||||||
|
@ -1988,16 +1990,8 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
||||||
*/
|
*/
|
||||||
if (auto nt = get<NegationType>(discriminant))
|
if (auto nt = get<NegationType>(discriminant))
|
||||||
{
|
{
|
||||||
if (FFlag::LuauRemoveNotAnyHack)
|
if (get<NoRefineType>(follow(nt->ty)))
|
||||||
{
|
return {target, {}};
|
||||||
if (get<NoRefineType>(follow(nt->ty)))
|
|
||||||
return {target, {}};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (get<AnyType>(follow(nt->ty)))
|
|
||||||
return {target, {}};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
|
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
|
||||||
|
@ -2070,7 +2064,7 @@ TypeFunctionReductionResult<TypeId> singletonTypeFunction(
|
||||||
return {std::nullopt, Reduction::MaybeOk, {type}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {type}, {}};
|
||||||
|
|
||||||
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
||||||
if (ctx->solver)
|
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, type);
|
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, type);
|
||||||
if (!maybeGeneralized)
|
if (!maybeGeneralized)
|
||||||
|
@ -2091,6 +2085,43 @@ TypeFunctionReductionResult<TypeId> singletonTypeFunction(
|
||||||
return {ctx->builtins->unknownType, Reduction::MaybeOk, {}, {}};
|
return {ctx->builtins->unknownType, Reduction::MaybeOk, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CollectUnionTypeOptions : TypeOnceVisitor
|
||||||
|
{
|
||||||
|
NotNull<TypeFunctionContext> ctx;
|
||||||
|
DenseHashSet<TypeId> options{nullptr};
|
||||||
|
DenseHashSet<TypeId> blockingTypes{nullptr};
|
||||||
|
|
||||||
|
explicit CollectUnionTypeOptions(NotNull<TypeFunctionContext> ctx)
|
||||||
|
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
||||||
|
, ctx(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty) override
|
||||||
|
{
|
||||||
|
options.insert(ty);
|
||||||
|
if (isPending(ty, ctx->solver))
|
||||||
|
blockingTypes.insert(ty);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypePackId tp) override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
|
||||||
|
{
|
||||||
|
if (tfit.function->name != builtinTypeFunctions().unionFunc.name)
|
||||||
|
{
|
||||||
|
options.insert(ty);
|
||||||
|
blockingTypes.insert(ty);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
TypeFunctionReductionResult<TypeId> unionTypeFunction(
|
TypeFunctionReductionResult<TypeId> unionTypeFunction(
|
||||||
TypeId instance,
|
TypeId instance,
|
||||||
const std::vector<TypeId>& typeParams,
|
const std::vector<TypeId>& typeParams,
|
||||||
|
@ -2108,6 +2139,35 @@ TypeFunctionReductionResult<TypeId> unionTypeFunction(
|
||||||
if (typeParams.size() == 1)
|
if (typeParams.size() == 1)
|
||||||
return {follow(typeParams[0]), Reduction::MaybeOk, {}, {}};
|
return {follow(typeParams[0]), Reduction::MaybeOk, {}, {}};
|
||||||
|
|
||||||
|
if (FFlag::LuauClipNestedAndRecursiveUnion)
|
||||||
|
{
|
||||||
|
|
||||||
|
CollectUnionTypeOptions collector{ctx};
|
||||||
|
collector.traverse(instance);
|
||||||
|
|
||||||
|
if (!collector.blockingTypes.empty())
|
||||||
|
{
|
||||||
|
std::vector<TypeId> blockingTypes{collector.blockingTypes.begin(), collector.blockingTypes.end()};
|
||||||
|
return {std::nullopt, Reduction::MaybeOk, std::move(blockingTypes), {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId resultTy = ctx->builtins->neverType;
|
||||||
|
for (auto ty : collector.options)
|
||||||
|
{
|
||||||
|
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
|
||||||
|
// This condition might fire if one of the arguments to this type
|
||||||
|
// function is a free type somewhere deep in a nested union or
|
||||||
|
// intersection type, even though we ran a pass above to capture
|
||||||
|
// some blocked types.
|
||||||
|
if (!result.blockedTypes.empty())
|
||||||
|
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
||||||
|
|
||||||
|
resultTy = result.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {resultTy, Reduction::MaybeOk, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
// we need to follow all of the type parameters.
|
// we need to follow all of the type parameters.
|
||||||
std::vector<TypeId> types;
|
std::vector<TypeId> types;
|
||||||
types.reserve(typeParams.size());
|
types.reserve(typeParams.size());
|
||||||
|
@ -2179,14 +2239,11 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
|
||||||
for (auto ty : typeParams)
|
for (auto ty : typeParams)
|
||||||
types.emplace_back(follow(ty));
|
types.emplace_back(follow(ty));
|
||||||
|
|
||||||
if (FFlag::LuauRemoveNotAnyHack)
|
// if we only have two parameters and one is `*no-refine*`, we're all done.
|
||||||
{
|
if (types.size() == 2 && get<NoRefineType>(types[1]))
|
||||||
// if we only have two parameters and one is `*no-refine*`, we're all done.
|
return {types[0], Reduction::MaybeOk, {}, {}};
|
||||||
if (types.size() == 2 && get<NoRefineType>(types[1]))
|
else if (types.size() == 2 && get<NoRefineType>(types[0]))
|
||||||
return {types[0], Reduction::MaybeOk, {}, {}};
|
return {types[1], Reduction::MaybeOk, {}, {}};
|
||||||
else if (types.size() == 2 && get<NoRefineType>(types[0]))
|
|
||||||
return {types[1], Reduction::MaybeOk, {}, {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
// check to see if the operand types are resolved enough, and wait to reduce if not
|
// check to see if the operand types are resolved enough, and wait to reduce if not
|
||||||
// if any of them are `never`, the intersection will always be `never`, so we can reduce directly.
|
// if any of them are `never`, the intersection will always be `never`, so we can reduce directly.
|
||||||
|
@ -2203,7 +2260,7 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
|
||||||
for (auto ty : types)
|
for (auto ty : types)
|
||||||
{
|
{
|
||||||
// skip any `*no-refine*` types.
|
// skip any `*no-refine*` types.
|
||||||
if (FFlag::LuauRemoveNotAnyHack && get<NoRefineType>(ty))
|
if (get<NoRefineType>(ty))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
|
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
|
||||||
|
@ -2722,6 +2779,215 @@ TypeFunctionReductionResult<TypeId> rawgetTypeFunction(
|
||||||
return indexFunctionImpl(typeParams, packParams, ctx, /* isRaw */ true);
|
return indexFunctionImpl(typeParams, packParams, ctx, /* isRaw */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
|
||||||
|
TypeId instance,
|
||||||
|
const std::vector<TypeId>& typeParams,
|
||||||
|
const std::vector<TypePackId>& packParams,
|
||||||
|
NotNull<TypeFunctionContext> ctx
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("setmetatable type function: encountered a type function instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Location location = ctx->constraint ? ctx->constraint->location : Location{};
|
||||||
|
|
||||||
|
TypeId targetTy = follow(typeParams.at(0));
|
||||||
|
TypeId metatableTy = follow(typeParams.at(1));
|
||||||
|
|
||||||
|
std::shared_ptr<const NormalizedType> targetNorm = ctx->normalizer->normalize(targetTy);
|
||||||
|
|
||||||
|
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||||
|
if (!targetNorm)
|
||||||
|
return {std::nullopt, Reduction::MaybeOk, {}, {}};
|
||||||
|
|
||||||
|
// cannot setmetatable on something without table parts.
|
||||||
|
if (!targetNorm->hasTables())
|
||||||
|
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||||
|
|
||||||
|
// we're trying to reject any type that has not normalized to a table or a union/intersection of tables.
|
||||||
|
if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() ||
|
||||||
|
targetNorm->hasNumbers() || targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() ||
|
||||||
|
targetNorm->hasFunctions() || targetNorm->hasTyvars() || targetNorm->hasClasses())
|
||||||
|
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||||
|
|
||||||
|
// if the supposed metatable is not a table, we will fail to reduce.
|
||||||
|
if (!get<TableType>(metatableTy) && !get<MetatableType>(metatableTy))
|
||||||
|
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||||
|
|
||||||
|
if (targetNorm->tables.size() == 1)
|
||||||
|
{
|
||||||
|
TypeId table = *targetNorm->tables.begin();
|
||||||
|
|
||||||
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||||
|
// the necessary state to do that, even if we intend to just eat the errors.
|
||||||
|
ErrorVec dummy;
|
||||||
|
|
||||||
|
std::optional<TypeId> metatableMetamethod = findMetatableEntry(ctx->builtins, dummy, table, "__metatable", location);
|
||||||
|
|
||||||
|
// if the `__metatable` metamethod is present, then the table is locked and we cannot `setmetatable` on it.
|
||||||
|
if (metatableMetamethod)
|
||||||
|
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||||
|
|
||||||
|
TypeId withMetatable = ctx->arena->addType(MetatableType{table, metatableTy});
|
||||||
|
|
||||||
|
return {withMetatable, Reduction::MaybeOk, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId result = ctx->builtins->neverType;
|
||||||
|
|
||||||
|
for (auto componentTy : targetNorm->tables)
|
||||||
|
{
|
||||||
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||||
|
// the necessary state to do that, even if we intend to just eat the errors.
|
||||||
|
ErrorVec dummy;
|
||||||
|
|
||||||
|
std::optional<TypeId> metatableMetamethod = findMetatableEntry(ctx->builtins, dummy, componentTy, "__metatable", location);
|
||||||
|
|
||||||
|
// if the `__metatable` metamethod is present, then the table is locked and we cannot `setmetatable` on it.
|
||||||
|
if (metatableMetamethod)
|
||||||
|
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||||
|
|
||||||
|
TypeId withMetatable = ctx->arena->addType(MetatableType{componentTy, metatableTy});
|
||||||
|
SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, result, withMetatable);
|
||||||
|
|
||||||
|
if (!simplified.blockedTypes.empty())
|
||||||
|
{
|
||||||
|
std::vector<TypeId> blockedTypes{};
|
||||||
|
blockedTypes.reserve(simplified.blockedTypes.size());
|
||||||
|
for (auto ty : simplified.blockedTypes)
|
||||||
|
blockedTypes.push_back(ty);
|
||||||
|
return {std::nullopt, Reduction::MaybeOk, blockedTypes, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
result = simplified.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {result, Reduction::MaybeOk, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
static TypeFunctionReductionResult<TypeId> getmetatableHelper(
|
||||||
|
TypeId targetTy,
|
||||||
|
const Location& location,
|
||||||
|
NotNull<TypeFunctionContext> ctx
|
||||||
|
)
|
||||||
|
{
|
||||||
|
targetTy = follow(targetTy);
|
||||||
|
|
||||||
|
std::optional<TypeId> metatable = std::nullopt;
|
||||||
|
bool erroneous = true;
|
||||||
|
|
||||||
|
if (auto table = get<TableType>(targetTy))
|
||||||
|
erroneous = false;
|
||||||
|
|
||||||
|
if (auto mt = get<MetatableType>(targetTy))
|
||||||
|
{
|
||||||
|
metatable = mt->metatable;
|
||||||
|
erroneous = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto clazz = get<ClassType>(targetTy))
|
||||||
|
{
|
||||||
|
metatable = clazz->metatable;
|
||||||
|
erroneous = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto primitive = get<PrimitiveType>(targetTy))
|
||||||
|
{
|
||||||
|
metatable = primitive->metatable;
|
||||||
|
erroneous = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto singleton = get<SingletonType>(targetTy))
|
||||||
|
{
|
||||||
|
if (get<StringSingleton>(singleton))
|
||||||
|
{
|
||||||
|
auto primitiveString = get<PrimitiveType>(ctx->builtins->stringType);
|
||||||
|
metatable = primitiveString->metatable;
|
||||||
|
}
|
||||||
|
erroneous = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (erroneous)
|
||||||
|
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||||
|
|
||||||
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||||
|
// the necessary state to do that, even if we intend to just eat the errors.
|
||||||
|
ErrorVec dummy;
|
||||||
|
|
||||||
|
std::optional<TypeId> metatableMetamethod = findMetatableEntry(ctx->builtins, dummy, targetTy, "__metatable", location);
|
||||||
|
|
||||||
|
if (metatableMetamethod)
|
||||||
|
return {metatableMetamethod, Reduction::MaybeOk, {}, {}};
|
||||||
|
|
||||||
|
if (metatable)
|
||||||
|
return {metatable, Reduction::MaybeOk, {}, {}};
|
||||||
|
|
||||||
|
return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFunctionReductionResult<TypeId> getmetatableTypeFunction(
|
||||||
|
TypeId instance,
|
||||||
|
const std::vector<TypeId>& typeParams,
|
||||||
|
const std::vector<TypePackId>& packParams,
|
||||||
|
NotNull<TypeFunctionContext> ctx
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (typeParams.size() != 1 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("getmetatable type function: encountered a type function instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Location location = ctx->constraint ? ctx->constraint->location : Location{};
|
||||||
|
|
||||||
|
TypeId targetTy = follow(typeParams.at(0));
|
||||||
|
|
||||||
|
if (isPending(targetTy, ctx->solver))
|
||||||
|
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
|
||||||
|
|
||||||
|
if (auto ut = get<UnionType>(targetTy))
|
||||||
|
{
|
||||||
|
std::vector<TypeId> options{};
|
||||||
|
options.reserve(ut->options.size());
|
||||||
|
|
||||||
|
for (auto option : ut->options)
|
||||||
|
{
|
||||||
|
TypeFunctionReductionResult<TypeId> result = getmetatableHelper(option, location, ctx);
|
||||||
|
|
||||||
|
if (!result.result)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
options.push_back(*result.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {ctx->arena->addType(UnionType{std::move(options)}), Reduction::MaybeOk, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto it = get<IntersectionType>(targetTy))
|
||||||
|
{
|
||||||
|
std::vector<TypeId> parts{};
|
||||||
|
parts.reserve(it->parts.size());
|
||||||
|
|
||||||
|
for (auto part : it->parts)
|
||||||
|
{
|
||||||
|
TypeFunctionReductionResult<TypeId> result = getmetatableHelper(part, location, ctx);
|
||||||
|
|
||||||
|
if (!result.result)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
parts.push_back(*result.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {ctx->arena->addType(IntersectionType{std::move(parts)}), Reduction::MaybeOk, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
return getmetatableHelper(targetTy, location, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BuiltinTypeFunctions::BuiltinTypeFunctions()
|
BuiltinTypeFunctions::BuiltinTypeFunctions()
|
||||||
: userFunc{"user", userDefinedTypeFunction}
|
: userFunc{"user", userDefinedTypeFunction}
|
||||||
, notFunc{"not", notTypeFunction}
|
, notFunc{"not", notTypeFunction}
|
||||||
|
@ -2748,6 +3014,8 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
|
||||||
, rawkeyofFunc{"rawkeyof", rawkeyofTypeFunction}
|
, rawkeyofFunc{"rawkeyof", rawkeyofTypeFunction}
|
||||||
, indexFunc{"index", indexTypeFunction}
|
, indexFunc{"index", indexTypeFunction}
|
||||||
, rawgetFunc{"rawget", rawgetTypeFunction}
|
, rawgetFunc{"rawget", rawgetTypeFunction}
|
||||||
|
, setmetatableFunc{"setmetatable", setmetatableTypeFunction}
|
||||||
|
, getmetatableFunc{"getmetatable", getmetatableTypeFunction}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2794,6 +3062,12 @@ void BuiltinTypeFunctions::addToScope(NotNull<TypeArena> arena, NotNull<Scope> s
|
||||||
|
|
||||||
scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc);
|
scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc);
|
||||||
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc);
|
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc);
|
||||||
|
|
||||||
|
if (FFlag::LuauMetatableTypeFunctions)
|
||||||
|
{
|
||||||
|
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc);
|
||||||
|
scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BuiltinTypeFunctions& builtinTypeFunctions()
|
const BuiltinTypeFunctions& builtinTypeFunctions()
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixInner)
|
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixInner)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixNoReadWrite)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunGenerics)
|
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunGenerics)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunCloneTail)
|
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunCloneTail)
|
||||||
|
|
||||||
|
@ -683,10 +682,8 @@ static int readTableProp(lua_State* L)
|
||||||
auto prop = tftt->props.at(tfsst->value);
|
auto prop = tftt->props.at(tfsst->value);
|
||||||
if (prop.readTy)
|
if (prop.readTy)
|
||||||
allocTypeUserData(L, (*prop.readTy)->type);
|
allocTypeUserData(L, (*prop.readTy)->type);
|
||||||
else if (FFlag::LuauUserTypeFunFixNoReadWrite)
|
|
||||||
lua_pushnil(L);
|
|
||||||
else
|
else
|
||||||
luaL_error(L, "type.readproperty: property %s is write-only, and therefore does not have a read type.", tfsst->value.c_str());
|
lua_pushnil(L);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -723,10 +720,8 @@ static int writeTableProp(lua_State* L)
|
||||||
auto prop = tftt->props.at(tfsst->value);
|
auto prop = tftt->props.at(tfsst->value);
|
||||||
if (prop.writeTy)
|
if (prop.writeTy)
|
||||||
allocTypeUserData(L, (*prop.writeTy)->type);
|
allocTypeUserData(L, (*prop.writeTy)->type);
|
||||||
else if (FFlag::LuauUserTypeFunFixNoReadWrite)
|
|
||||||
lua_pushnil(L);
|
|
||||||
else
|
else
|
||||||
luaL_error(L, "type.writeproperty: property %s is read-only, and therefore does not have a write type.", tfsst->value.c_str());
|
lua_pushnil(L);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,8 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
|
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauOldSolverCreatesChildScopePointers)
|
LUAU_FASTFLAGVARIABLE(LuauOldSolverCreatesChildScopePointers)
|
||||||
|
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -761,8 +763,12 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatRepeat& state
|
||||||
|
|
||||||
struct Demoter : Substitution
|
struct Demoter : Substitution
|
||||||
{
|
{
|
||||||
Demoter(TypeArena* arena)
|
TypeArena* arena = nullptr;
|
||||||
|
NotNull<BuiltinTypes> builtins;
|
||||||
|
Demoter(TypeArena* arena, NotNull<BuiltinTypes> builtins)
|
||||||
: Substitution(TxnLog::empty(), arena)
|
: Substitution(TxnLog::empty(), arena)
|
||||||
|
, arena(arena)
|
||||||
|
, builtins(builtins)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,7 +794,8 @@ struct Demoter : Substitution
|
||||||
{
|
{
|
||||||
auto ftv = get<FreeType>(ty);
|
auto ftv = get<FreeType>(ty);
|
||||||
LUAU_ASSERT(ftv);
|
LUAU_ASSERT(ftv);
|
||||||
return addType(FreeType{demotedLevel(ftv->level)});
|
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtins, demotedLevel(ftv->level))
|
||||||
|
: addType(FreeType{demotedLevel(ftv->level)});
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePackId clean(TypePackId tp) override
|
TypePackId clean(TypePackId tp) override
|
||||||
|
@ -835,7 +842,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatReturn& retur
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Demoter demoter{¤tModule->internalTypes};
|
Demoter demoter{¤tModule->internalTypes, builtinTypes};
|
||||||
demoter.demote(expectedTypes);
|
demoter.demote(expectedTypes);
|
||||||
|
|
||||||
TypePackId retPack = checkExprList(scope, return_.location, return_.list, false, {}, expectedTypes).type;
|
TypePackId retPack = checkExprList(scope, return_.location, return_.list, false, {}, expectedTypes).type;
|
||||||
|
@ -4408,7 +4415,7 @@ std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Demoter demoter{¤tModule->internalTypes};
|
Demoter demoter{¤tModule->internalTypes, builtinTypes};
|
||||||
demoter.demote(expectedTypes);
|
demoter.demote(expectedTypes);
|
||||||
|
|
||||||
return expectedTypes;
|
return expectedTypes;
|
||||||
|
@ -5273,7 +5280,8 @@ TypeId TypeChecker::freshType(const ScopePtr& scope)
|
||||||
|
|
||||||
TypeId TypeChecker::freshType(TypeLevel level)
|
TypeId TypeChecker::freshType(TypeLevel level)
|
||||||
{
|
{
|
||||||
return currentModule->internalTypes.addType(Type(FreeType(level)));
|
return FFlag::LuauFreeTypesMustHaveBounds ? currentModule->internalTypes.freshType(builtinTypes, level)
|
||||||
|
: currentModule->internalTypes.addType(Type(FreeType(level)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId TypeChecker::singletonType(bool value)
|
TypeId TypeChecker::singletonType(bool value)
|
||||||
|
@ -5718,6 +5726,12 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
|
||||||
}
|
}
|
||||||
else if (const auto& un = annotation.as<AstTypeUnion>())
|
else if (const auto& un = annotation.as<AstTypeUnion>())
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
{
|
||||||
|
if (un->types.size == 1)
|
||||||
|
return resolveType(scope, *un->types.data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<TypeId> types;
|
std::vector<TypeId> types;
|
||||||
for (AstType* ann : un->types)
|
for (AstType* ann : un->types)
|
||||||
types.push_back(resolveType(scope, *ann));
|
types.push_back(resolveType(scope, *ann));
|
||||||
|
@ -5726,12 +5740,22 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
|
||||||
}
|
}
|
||||||
else if (const auto& un = annotation.as<AstTypeIntersection>())
|
else if (const auto& un = annotation.as<AstTypeIntersection>())
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
{
|
||||||
|
if (un->types.size == 1)
|
||||||
|
return resolveType(scope, *un->types.data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<TypeId> types;
|
std::vector<TypeId> types;
|
||||||
for (AstType* ann : un->types)
|
for (AstType* ann : un->types)
|
||||||
types.push_back(resolveType(scope, *ann));
|
types.push_back(resolveType(scope, *ann));
|
||||||
|
|
||||||
return addType(IntersectionType{types});
|
return addType(IntersectionType{types});
|
||||||
}
|
}
|
||||||
|
else if (const auto& g = annotation.as<AstTypeGroup>())
|
||||||
|
{
|
||||||
|
return resolveType(scope, *g->type);
|
||||||
|
}
|
||||||
else if (const auto& tsb = annotation.as<AstTypeSingletonBool>())
|
else if (const auto& tsb = annotation.as<AstTypeSingletonBool>())
|
||||||
{
|
{
|
||||||
return singletonType(tsb->value);
|
return singletonType(tsb->value);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -12,6 +13,7 @@
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2);
|
||||||
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
|
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
|
||||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
|
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -323,7 +325,7 @@ TypePack extendTypePack(
|
||||||
trackInteriorFreeType(ftp->scope, t);
|
trackInteriorFreeType(ftp->scope, t);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
t = arena.freshType(ftp->scope);
|
t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
newPack.head.push_back(t);
|
newPack.head.push_back(t);
|
||||||
|
|
|
@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering)
|
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart)
|
LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart)
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -1648,7 +1649,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
||||||
if (FFlag::LuauSolverV2)
|
if (FFlag::LuauSolverV2)
|
||||||
return freshType(NotNull{types}, builtinTypes, scope);
|
return freshType(NotNull{types}, builtinTypes, scope);
|
||||||
else
|
else
|
||||||
return types->freshType(scope, level);
|
return FFlag::LuauFreeTypesMustHaveBounds ? types->freshType(builtinTypes, scope, level) : types->freshType_DEPRECATED(scope, level);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});
|
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});
|
||||||
|
|
|
@ -1204,6 +1204,18 @@ public:
|
||||||
const AstArray<char> value;
|
const AstArray<char> value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AstTypeGroup : public AstType
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_RTTI(AstTypeGroup)
|
||||||
|
|
||||||
|
explicit AstTypeGroup(const Location& location, AstType* type);
|
||||||
|
|
||||||
|
void visit(AstVisitor* visitor) override;
|
||||||
|
|
||||||
|
AstType* type;
|
||||||
|
};
|
||||||
|
|
||||||
class AstTypePack : public AstNode
|
class AstTypePack : public AstNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -1470,6 +1482,10 @@ public:
|
||||||
{
|
{
|
||||||
return visit(static_cast<AstType*>(node));
|
return visit(static_cast<AstType*>(node));
|
||||||
}
|
}
|
||||||
|
virtual bool visit(class AstTypeGroup* node)
|
||||||
|
{
|
||||||
|
return visit(static_cast<AstType*>(node));
|
||||||
|
}
|
||||||
virtual bool visit(class AstTypeError* node)
|
virtual bool visit(class AstTypeError* node)
|
||||||
{
|
{
|
||||||
return visit(static_cast<AstType*>(node));
|
return visit(static_cast<AstType*>(node));
|
||||||
|
|
334
Ast/include/Luau/Cst.h
Normal file
334
Ast/include/Luau/Cst.h
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Location.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
extern int gCstRttiIndex;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct CstRtti
|
||||||
|
{
|
||||||
|
static const int value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
const int CstRtti<T>::value = ++gCstRttiIndex;
|
||||||
|
|
||||||
|
#define LUAU_CST_RTTI(Class) \
|
||||||
|
static int CstClassIndex() \
|
||||||
|
{ \
|
||||||
|
return CstRtti<Class>::value; \
|
||||||
|
}
|
||||||
|
|
||||||
|
class CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit CstNode(int classIndex)
|
||||||
|
: classIndex(classIndex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool is() const
|
||||||
|
{
|
||||||
|
return classIndex == T::CstClassIndex();
|
||||||
|
}
|
||||||
|
template<typename T>
|
||||||
|
T* as()
|
||||||
|
{
|
||||||
|
return classIndex == T::CstClassIndex() ? static_cast<T*>(this) : nullptr;
|
||||||
|
}
|
||||||
|
template<typename T>
|
||||||
|
const T* as() const
|
||||||
|
{
|
||||||
|
return classIndex == T::CstClassIndex() ? static_cast<const T*>(this) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int classIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstExprConstantNumber : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstExprConstantNumber)
|
||||||
|
|
||||||
|
explicit CstExprConstantNumber(const AstArray<char>& value);
|
||||||
|
|
||||||
|
AstArray<char> value;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstExprConstantString : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstExprConstantNumber)
|
||||||
|
|
||||||
|
enum QuoteStyle
|
||||||
|
{
|
||||||
|
QuotedSingle,
|
||||||
|
QuotedDouble,
|
||||||
|
QuotedRaw,
|
||||||
|
QuotedInterp,
|
||||||
|
};
|
||||||
|
|
||||||
|
CstExprConstantString(AstArray<char> sourceString, QuoteStyle quoteStyle, unsigned int blockDepth);
|
||||||
|
|
||||||
|
AstArray<char> sourceString;
|
||||||
|
QuoteStyle quoteStyle;
|
||||||
|
unsigned int blockDepth;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstExprCall : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstExprCall)
|
||||||
|
|
||||||
|
CstExprCall(std::optional<Position> openParens, std::optional<Position> closeParens, AstArray<Position> commaPositions);
|
||||||
|
|
||||||
|
std::optional<Position> openParens;
|
||||||
|
std::optional<Position> closeParens;
|
||||||
|
AstArray<Position> commaPositions;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstExprIndexExpr : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstExprIndexExpr)
|
||||||
|
|
||||||
|
CstExprIndexExpr(Position openBracketPosition, Position closeBracketPosition);
|
||||||
|
|
||||||
|
Position openBracketPosition;
|
||||||
|
Position closeBracketPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstExprTable : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstExprTable)
|
||||||
|
|
||||||
|
enum Separator
|
||||||
|
{
|
||||||
|
Comma,
|
||||||
|
Semicolon,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Item
|
||||||
|
{
|
||||||
|
std::optional<Position> indexerOpenPosition; // '[', only if Kind == General
|
||||||
|
std::optional<Position> indexerClosePosition; // ']', only if Kind == General
|
||||||
|
std::optional<Position> equalsPosition; // only if Kind != List
|
||||||
|
std::optional<Separator> separator; // may be missing for last Item
|
||||||
|
std::optional<Position> separatorPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit CstExprTable(const AstArray<Item>& items);
|
||||||
|
|
||||||
|
AstArray<Item> items;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Shared between unary and binary, should we split?
|
||||||
|
class CstExprOp : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstExprOp)
|
||||||
|
|
||||||
|
explicit CstExprOp(Position opPosition);
|
||||||
|
|
||||||
|
Position opPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstExprIfElse : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstExprIfElse)
|
||||||
|
|
||||||
|
CstExprIfElse(Position thenPosition, Position elsePosition, bool isElseIf);
|
||||||
|
|
||||||
|
Position thenPosition;
|
||||||
|
Position elsePosition;
|
||||||
|
bool isElseIf;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstExprInterpString : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstExprInterpString)
|
||||||
|
|
||||||
|
explicit CstExprInterpString(AstArray<AstArray<char>> sourceStrings, AstArray<Position> stringPositions);
|
||||||
|
|
||||||
|
AstArray<AstArray<char>> sourceStrings;
|
||||||
|
AstArray<Position> stringPositions;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstStatDo : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatDo)
|
||||||
|
|
||||||
|
explicit CstStatDo(Position endPosition);
|
||||||
|
|
||||||
|
Position endPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstStatRepeat : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatRepeat)
|
||||||
|
|
||||||
|
explicit CstStatRepeat(Position untilPosition);
|
||||||
|
|
||||||
|
Position untilPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstStatReturn : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatReturn)
|
||||||
|
|
||||||
|
explicit CstStatReturn(AstArray<Position> commaPositions);
|
||||||
|
|
||||||
|
AstArray<Position> commaPositions;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstStatLocal : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatLocal)
|
||||||
|
|
||||||
|
CstStatLocal(AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions);
|
||||||
|
|
||||||
|
AstArray<Position> varsCommaPositions;
|
||||||
|
AstArray<Position> valuesCommaPositions;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstStatFor : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatFor)
|
||||||
|
|
||||||
|
CstStatFor(Position equalsPosition, Position endCommaPosition, std::optional<Position> stepCommaPosition);
|
||||||
|
|
||||||
|
Position equalsPosition;
|
||||||
|
Position endCommaPosition;
|
||||||
|
std::optional<Position> stepCommaPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstStatForIn : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatForIn)
|
||||||
|
|
||||||
|
CstStatForIn(AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions);
|
||||||
|
|
||||||
|
AstArray<Position> varsCommaPositions;
|
||||||
|
AstArray<Position> valuesCommaPositions;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstStatAssign : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatAssign)
|
||||||
|
|
||||||
|
CstStatAssign(AstArray<Position> varsCommaPositions, Position equalsPosition, AstArray<Position> valuesCommaPositions);
|
||||||
|
|
||||||
|
AstArray<Position> varsCommaPositions;
|
||||||
|
Position equalsPosition;
|
||||||
|
AstArray<Position> valuesCommaPositions;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstStatCompoundAssign : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatCompoundAssign)
|
||||||
|
|
||||||
|
explicit CstStatCompoundAssign(Position opPosition);
|
||||||
|
|
||||||
|
Position opPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstStatLocalFunction : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatLocalFunction)
|
||||||
|
|
||||||
|
explicit CstStatLocalFunction(Position functionKeywordPosition);
|
||||||
|
|
||||||
|
Position functionKeywordPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstTypeReference : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstTypeReference)
|
||||||
|
|
||||||
|
CstTypeReference(
|
||||||
|
std::optional<Position> prefixPointPosition,
|
||||||
|
Position openParametersPosition,
|
||||||
|
AstArray<Position> parametersCommaPositions,
|
||||||
|
Position closeParametersPosition
|
||||||
|
);
|
||||||
|
|
||||||
|
std::optional<Position> prefixPointPosition;
|
||||||
|
Position openParametersPosition;
|
||||||
|
AstArray<Position> parametersCommaPositions;
|
||||||
|
Position closeParametersPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstTypeTable : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstTypeTable)
|
||||||
|
|
||||||
|
struct Item
|
||||||
|
{
|
||||||
|
enum struct Kind
|
||||||
|
{
|
||||||
|
Indexer,
|
||||||
|
Property,
|
||||||
|
StringProperty,
|
||||||
|
};
|
||||||
|
|
||||||
|
Kind kind;
|
||||||
|
Position indexerOpenPosition; // '[', only if Kind != Property
|
||||||
|
Position indexerClosePosition; // ']' only if Kind != Property
|
||||||
|
Position colonPosition;
|
||||||
|
std::optional<CstExprTable::Separator> separator; // may be missing for last Item
|
||||||
|
std::optional<Position> separatorPosition;
|
||||||
|
|
||||||
|
CstExprConstantString* stringInfo = nullptr; // only if Kind == StringProperty
|
||||||
|
};
|
||||||
|
|
||||||
|
CstTypeTable(AstArray<Item> items, bool isArray);
|
||||||
|
|
||||||
|
AstArray<Item> items;
|
||||||
|
bool isArray = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstTypeTypeof : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstTypeTypeof)
|
||||||
|
|
||||||
|
CstTypeTypeof(Position openPosition, Position closePosition);
|
||||||
|
|
||||||
|
Position openPosition;
|
||||||
|
Position closePosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CstTypeSingletonString : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstTypeSingletonString)
|
||||||
|
|
||||||
|
CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth);
|
||||||
|
|
||||||
|
AstArray<char> sourceString;
|
||||||
|
CstExprConstantString::QuoteStyle quoteStyle;
|
||||||
|
unsigned int blockDepth;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -87,6 +87,12 @@ struct Lexeme
|
||||||
Reserved_END
|
Reserved_END
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum struct QuoteStyle
|
||||||
|
{
|
||||||
|
Single,
|
||||||
|
Double,
|
||||||
|
};
|
||||||
|
|
||||||
Type type;
|
Type type;
|
||||||
Location location;
|
Location location;
|
||||||
|
|
||||||
|
@ -111,6 +117,8 @@ public:
|
||||||
Lexeme(const Location& location, Type type, const char* name);
|
Lexeme(const Location& location, Type type, const char* name);
|
||||||
|
|
||||||
unsigned int getLength() const;
|
unsigned int getLength() const;
|
||||||
|
unsigned int getBlockDepth() const;
|
||||||
|
QuoteStyle getQuoteStyle() const;
|
||||||
|
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,8 @@ struct ParseOptions
|
||||||
bool allowDeclarationSyntax = false;
|
bool allowDeclarationSyntax = false;
|
||||||
bool captureComments = false;
|
bool captureComments = false;
|
||||||
std::optional<FragmentParseResumeSettings> parseFragment = std::nullopt;
|
std::optional<FragmentParseResumeSettings> parseFragment = std::nullopt;
|
||||||
|
bool storeCstData = false;
|
||||||
|
bool noErrorLimit = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
class AstStatBlock;
|
class AstStatBlock;
|
||||||
|
class CstNode;
|
||||||
|
|
||||||
class ParseError : public std::exception
|
class ParseError : public std::exception
|
||||||
{
|
{
|
||||||
|
@ -55,6 +56,8 @@ struct Comment
|
||||||
Location location;
|
Location location;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using CstNodeMap = DenseHashMap<AstNode*, CstNode*>;
|
||||||
|
|
||||||
struct ParseResult
|
struct ParseResult
|
||||||
{
|
{
|
||||||
AstStatBlock* root;
|
AstStatBlock* root;
|
||||||
|
@ -64,6 +67,8 @@ struct ParseResult
|
||||||
std::vector<ParseError> errors;
|
std::vector<ParseError> errors;
|
||||||
|
|
||||||
std::vector<Comment> commentLocations;
|
std::vector<Comment> commentLocations;
|
||||||
|
|
||||||
|
CstNodeMap cstNodeMap{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr const char* kParseNameError = "%error-id%";
|
static constexpr const char* kParseNameError = "%error-id%";
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/Cst.h"
|
||||||
|
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -173,14 +174,18 @@ private:
|
||||||
);
|
);
|
||||||
|
|
||||||
// explist ::= {exp `,'} exp
|
// explist ::= {exp `,'} exp
|
||||||
void parseExprList(TempVector<AstExpr*>& result);
|
void parseExprList(TempVector<AstExpr*>& result, TempVector<Position>* commaPositions = nullptr);
|
||||||
|
|
||||||
// binding ::= Name [`:` Type]
|
// binding ::= Name [`:` Type]
|
||||||
Binding parseBinding();
|
Binding parseBinding();
|
||||||
|
|
||||||
// bindinglist ::= (binding | `...') {`,' bindinglist}
|
// bindinglist ::= (binding | `...') {`,' bindinglist}
|
||||||
// Returns the location of the vararg ..., or std::nullopt if the function is not vararg.
|
// Returns the location of the vararg ..., or std::nullopt if the function is not vararg.
|
||||||
std::tuple<bool, Location, AstTypePack*> parseBindingList(TempVector<Binding>& result, bool allowDot3 = false);
|
std::tuple<bool, Location, AstTypePack*> parseBindingList(
|
||||||
|
TempVector<Binding>& result,
|
||||||
|
bool allowDot3 = false,
|
||||||
|
TempVector<Position>* commaPositions = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
AstType* parseOptionalType();
|
AstType* parseOptionalType();
|
||||||
|
|
||||||
|
@ -201,7 +206,17 @@ private:
|
||||||
std::optional<AstTypeList> parseOptionalReturnType();
|
std::optional<AstTypeList> parseOptionalReturnType();
|
||||||
std::pair<Location, AstTypeList> parseReturnType();
|
std::pair<Location, AstTypeList> parseReturnType();
|
||||||
|
|
||||||
AstTableIndexer* parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation);
|
struct TableIndexerResult
|
||||||
|
{
|
||||||
|
AstTableIndexer* node;
|
||||||
|
Position indexerOpenPosition;
|
||||||
|
Position indexerClosePosition;
|
||||||
|
Position colonPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation);
|
||||||
|
// Remove with FFlagLuauStoreCSTData
|
||||||
|
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation);
|
||||||
|
|
||||||
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
|
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
|
||||||
AstType* parseFunctionTypeTail(
|
AstType* parseFunctionTypeTail(
|
||||||
|
@ -259,6 +274,8 @@ private:
|
||||||
// args ::= `(' [explist] `)' | tableconstructor | String
|
// args ::= `(' [explist] `)' | tableconstructor | String
|
||||||
AstExpr* parseFunctionArgs(AstExpr* func, bool self);
|
AstExpr* parseFunctionArgs(AstExpr* func, bool self);
|
||||||
|
|
||||||
|
std::optional<CstExprTable::Separator> tableSeparator();
|
||||||
|
|
||||||
// tableconstructor ::= `{' [fieldlist] `}'
|
// tableconstructor ::= `{' [fieldlist] `}'
|
||||||
// fieldlist ::= field {fieldsep field} [fieldsep]
|
// fieldlist ::= field {fieldsep field} [fieldsep]
|
||||||
// field ::= `[' exp `]' `=' exp | Name `=' exp | exp
|
// field ::= `[' exp `]' `=' exp | Name `=' exp | exp
|
||||||
|
@ -280,9 +297,13 @@ private:
|
||||||
std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> parseGenericTypeList(bool withDefaultValues);
|
std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> parseGenericTypeList(bool withDefaultValues);
|
||||||
|
|
||||||
// `<' Type[, ...] `>'
|
// `<' Type[, ...] `>'
|
||||||
AstArray<AstTypeOrPack> parseTypeParams();
|
AstArray<AstTypeOrPack> parseTypeParams(
|
||||||
|
Position* openingPosition = nullptr,
|
||||||
|
TempVector<Position>* commaPositions = nullptr,
|
||||||
|
Position* closingPosition = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
std::optional<AstArray<char>> parseCharArray();
|
std::optional<AstArray<char>> parseCharArray(AstArray<char>* originalString = nullptr);
|
||||||
AstExpr* parseString();
|
AstExpr* parseString();
|
||||||
AstExpr* parseNumber();
|
AstExpr* parseNumber();
|
||||||
|
|
||||||
|
@ -292,6 +313,9 @@ private:
|
||||||
|
|
||||||
void restoreLocals(unsigned int offset);
|
void restoreLocals(unsigned int offset);
|
||||||
|
|
||||||
|
/// Returns string quote style and block depth
|
||||||
|
std::pair<CstExprConstantString::QuoteStyle, unsigned int> extractStringDetails();
|
||||||
|
|
||||||
// check that parser is at lexeme/symbol, move to next lexeme/symbol on success, report failure and continue on failure
|
// check that parser is at lexeme/symbol, move to next lexeme/symbol on success, report failure and continue on failure
|
||||||
bool expectAndConsume(char value, const char* context = nullptr);
|
bool expectAndConsume(char value, const char* context = nullptr);
|
||||||
bool expectAndConsume(Lexeme::Type type, const char* context = nullptr);
|
bool expectAndConsume(Lexeme::Type type, const char* context = nullptr);
|
||||||
|
@ -435,6 +459,7 @@ private:
|
||||||
std::vector<AstAttr*> scratchAttr;
|
std::vector<AstAttr*> scratchAttr;
|
||||||
std::vector<AstStat*> scratchStat;
|
std::vector<AstStat*> scratchStat;
|
||||||
std::vector<AstArray<char>> scratchString;
|
std::vector<AstArray<char>> scratchString;
|
||||||
|
std::vector<AstArray<char>> scratchString2;
|
||||||
std::vector<AstExpr*> scratchExpr;
|
std::vector<AstExpr*> scratchExpr;
|
||||||
std::vector<AstExpr*> scratchExprAux;
|
std::vector<AstExpr*> scratchExprAux;
|
||||||
std::vector<AstName> scratchName;
|
std::vector<AstName> scratchName;
|
||||||
|
@ -442,15 +467,20 @@ private:
|
||||||
std::vector<Binding> scratchBinding;
|
std::vector<Binding> scratchBinding;
|
||||||
std::vector<AstLocal*> scratchLocal;
|
std::vector<AstLocal*> scratchLocal;
|
||||||
std::vector<AstTableProp> scratchTableTypeProps;
|
std::vector<AstTableProp> scratchTableTypeProps;
|
||||||
|
std::vector<CstTypeTable::Item> scratchCstTableTypeProps;
|
||||||
std::vector<AstType*> scratchType;
|
std::vector<AstType*> scratchType;
|
||||||
std::vector<AstTypeOrPack> scratchTypeOrPack;
|
std::vector<AstTypeOrPack> scratchTypeOrPack;
|
||||||
std::vector<AstDeclaredClassProp> scratchDeclaredClassProps;
|
std::vector<AstDeclaredClassProp> scratchDeclaredClassProps;
|
||||||
std::vector<AstExprTable::Item> scratchItem;
|
std::vector<AstExprTable::Item> scratchItem;
|
||||||
|
std::vector<CstExprTable::Item> scratchCstItem;
|
||||||
std::vector<AstArgumentName> scratchArgName;
|
std::vector<AstArgumentName> scratchArgName;
|
||||||
std::vector<AstGenericType> scratchGenericTypes;
|
std::vector<AstGenericType> scratchGenericTypes;
|
||||||
std::vector<AstGenericTypePack> scratchGenericTypePacks;
|
std::vector<AstGenericTypePack> scratchGenericTypePacks;
|
||||||
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
||||||
|
std::vector<Position> scratchPosition;
|
||||||
std::string scratchData;
|
std::string scratchData;
|
||||||
|
|
||||||
|
CstNodeMap cstNodeMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -1091,6 +1091,18 @@ void AstTypeSingletonString::visit(AstVisitor* visitor)
|
||||||
visitor->visit(this);
|
visitor->visit(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstTypeGroup::AstTypeGroup(const Location& location, AstType* type)
|
||||||
|
: AstType(ClassIndex(), location)
|
||||||
|
, type(type)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AstTypeGroup::visit(AstVisitor* visitor)
|
||||||
|
{
|
||||||
|
if (visitor->visit(this))
|
||||||
|
type->visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
AstTypeError::AstTypeError(const Location& location, const AstArray<AstType*>& types, bool isMissing, unsigned messageIndex)
|
AstTypeError::AstTypeError(const Location& location, const AstArray<AstType*>& types, bool isMissing, unsigned messageIndex)
|
||||||
: AstType(ClassIndex(), location)
|
: AstType(ClassIndex(), location)
|
||||||
, types(types)
|
, types(types)
|
||||||
|
|
169
Ast/src/Cst.cpp
Normal file
169
Ast/src/Cst.cpp
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Luau/Ast.h"
|
||||||
|
#include "Luau/Cst.h"
|
||||||
|
#include "Luau/Common.h"
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
int gCstRttiIndex = 0;
|
||||||
|
|
||||||
|
CstExprConstantNumber::CstExprConstantNumber(const AstArray<char>& value)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstExprConstantString::CstExprConstantString(AstArray<char> sourceString, QuoteStyle quoteStyle, unsigned int blockDepth)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, sourceString(sourceString)
|
||||||
|
, quoteStyle(quoteStyle)
|
||||||
|
, blockDepth(blockDepth)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(blockDepth == 0 || quoteStyle == QuoteStyle::QuotedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
CstExprCall::CstExprCall(std::optional<Position> openParens, std::optional<Position> closeParens, AstArray<Position> commaPositions)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, openParens(openParens)
|
||||||
|
, closeParens(closeParens)
|
||||||
|
, commaPositions(commaPositions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstExprIndexExpr::CstExprIndexExpr(Position openBracketPosition, Position closeBracketPosition)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, openBracketPosition(openBracketPosition)
|
||||||
|
, closeBracketPosition(closeBracketPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstExprTable::CstExprTable(const AstArray<Item>& items)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, items(items)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstExprOp::CstExprOp(Position opPosition)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, opPosition(opPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstExprIfElse::CstExprIfElse(Position thenPosition, Position elsePosition, bool isElseIf)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, thenPosition(thenPosition)
|
||||||
|
, elsePosition(elsePosition)
|
||||||
|
, isElseIf(isElseIf)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstExprInterpString::CstExprInterpString(AstArray<AstArray<char>> sourceStrings, AstArray<Position> stringPositions)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, sourceStrings(sourceStrings)
|
||||||
|
, stringPositions(stringPositions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstStatDo::CstStatDo(Position endPosition)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, endPosition(endPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstStatRepeat::CstStatRepeat(Position untilPosition)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, untilPosition(untilPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstStatReturn::CstStatReturn(AstArray<Position> commaPositions)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, commaPositions(commaPositions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstStatLocal::CstStatLocal(AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, varsCommaPositions(varsCommaPositions)
|
||||||
|
, valuesCommaPositions(valuesCommaPositions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstStatFor::CstStatFor(Position equalsPosition, Position endCommaPosition, std::optional<Position> stepCommaPosition)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, equalsPosition(equalsPosition)
|
||||||
|
, endCommaPosition(endCommaPosition)
|
||||||
|
, stepCommaPosition(stepCommaPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstStatForIn::CstStatForIn(AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, varsCommaPositions(varsCommaPositions)
|
||||||
|
, valuesCommaPositions(valuesCommaPositions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstStatAssign::CstStatAssign(
|
||||||
|
AstArray<Position> varsCommaPositions,
|
||||||
|
Position equalsPosition,
|
||||||
|
AstArray<Position> valuesCommaPositions
|
||||||
|
)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, varsCommaPositions(varsCommaPositions)
|
||||||
|
, equalsPosition(equalsPosition)
|
||||||
|
, valuesCommaPositions(valuesCommaPositions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstStatCompoundAssign::CstStatCompoundAssign(Position opPosition)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, opPosition(opPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstStatLocalFunction::CstStatLocalFunction(Position functionKeywordPosition)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, functionKeywordPosition(functionKeywordPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstTypeReference::CstTypeReference(
|
||||||
|
std::optional<Position> prefixPointPosition,
|
||||||
|
Position openParametersPosition,
|
||||||
|
AstArray<Position> parametersCommaPositions,
|
||||||
|
Position closeParametersPosition
|
||||||
|
)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, prefixPointPosition(prefixPointPosition)
|
||||||
|
, openParametersPosition(openParametersPosition)
|
||||||
|
, parametersCommaPositions(parametersCommaPositions)
|
||||||
|
, closeParametersPosition(closeParametersPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstTypeTable::CstTypeTable(AstArray<Item> items, bool isArray)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, items(items)
|
||||||
|
, isArray(isArray)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, openPosition(openPosition)
|
||||||
|
, closePosition(closePosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, sourceString(sourceString)
|
||||||
|
, quoteStyle(quoteStyle)
|
||||||
|
, blockDepth(blockDepth)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -306,6 +306,38 @@ static char unescape(char ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned int Lexeme::getBlockDepth() const
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(type == Lexeme::RawString || type == Lexeme::BlockComment);
|
||||||
|
|
||||||
|
// If we have a well-formed string, we are guaranteed to see 2 `]` characters after the end of the string contents
|
||||||
|
LUAU_ASSERT(*(data + length) == ']');
|
||||||
|
unsigned int depth = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
depth++;
|
||||||
|
} while (*(data + length + depth) != ']');
|
||||||
|
|
||||||
|
return depth - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexeme::QuoteStyle Lexeme::getQuoteStyle() const
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(type == Lexeme::QuotedString);
|
||||||
|
|
||||||
|
// If we have a well-formed string, we are guaranteed to see a closing delimiter after the string
|
||||||
|
LUAU_ASSERT(data);
|
||||||
|
|
||||||
|
char quote = *(data + length);
|
||||||
|
if (quote == '\'')
|
||||||
|
return Lexeme::QuoteStyle::Single;
|
||||||
|
else if (quote == '"')
|
||||||
|
return Lexeme::QuoteStyle::Double;
|
||||||
|
|
||||||
|
LUAU_ASSERT(!"Unknown quote style");
|
||||||
|
return Lexeme::QuoteStyle::Double; // unreachable, but required due to compiler warning
|
||||||
|
}
|
||||||
|
|
||||||
Lexer::Lexer(const char* buffer, size_t bufferSize, AstNameTable& names, Position startPosition)
|
Lexer::Lexer(const char* buffer, size_t bufferSize, AstNameTable& names, Position startPosition)
|
||||||
: buffer(buffer)
|
: buffer(buffer)
|
||||||
, bufferSize(bufferSize)
|
, bufferSize(bufferSize)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,9 +20,21 @@
|
||||||
|
|
||||||
#elif defined(__linux__) || defined(__APPLE__)
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
|
|
||||||
// Defined in unwind.h which may not be easily discoverable on various platforms
|
// __register_frame and __deregister_frame are defined in libgcc or libc++
|
||||||
extern "C" void __register_frame(const void*) __attribute__((weak));
|
// (depending on how it's built). We want to declare them as weak symbols
|
||||||
extern "C" void __deregister_frame(const void*) __attribute__((weak));
|
// so that if they're provided by a shared library, we'll use them, and if
|
||||||
|
// not, we'll disable some c++ exception handling support. However, if they're
|
||||||
|
// declared as weak and the definitions are linked in a static library
|
||||||
|
// that's not linked with whole-archive, then the symbols will technically be defined here,
|
||||||
|
// and the linker won't look for the strong ones in the library.
|
||||||
|
#ifndef LUAU_ENABLE_REGISTER_FRAME
|
||||||
|
#define REGISTER_FRAME_WEAK __attribute__((weak))
|
||||||
|
#else
|
||||||
|
#define REGISTER_FRAME_WEAK
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern "C" void __register_frame(const void*) REGISTER_FRAME_WEAK;
|
||||||
|
extern "C" void __deregister_frame(const void*) REGISTER_FRAME_WEAK;
|
||||||
|
|
||||||
extern "C" void __unw_add_dynamic_fde() __attribute__((weak));
|
extern "C" void __unw_add_dynamic_fde() __attribute__((weak));
|
||||||
#endif
|
#endif
|
||||||
|
@ -121,7 +133,7 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#elif defined(__linux__) || defined(__APPLE__)
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
if (!__register_frame)
|
if (!&__register_frame)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
visitFdeEntries(unwindData, __register_frame);
|
visitFdeEntries(unwindData, __register_frame);
|
||||||
|
@ -150,7 +162,7 @@ void destroyBlockUnwindInfo(void* context, void* unwindData)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#elif defined(__linux__) || defined(__APPLE__)
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
if (!__deregister_frame)
|
if (!&__deregister_frame)
|
||||||
{
|
{
|
||||||
CODEGEN_ASSERT(!"Cannot deregister unwind information");
|
CODEGEN_ASSERT(!"Cannot deregister unwind information");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -14,7 +14,7 @@ inline bool isFlagExperimental(const char* flag)
|
||||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||||
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
||||||
"StudioReportLuauAny2", // takes telemetry data for usage of any types
|
"StudioReportLuauAny2", // takes telemetry data for usage of any types
|
||||||
"LuauTableCloneClonesType2", // requires fixes in lua-apps code, terrifyingly
|
"LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly
|
||||||
"LuauSolverV2",
|
"LuauSolverV2",
|
||||||
// makes sure we always have at least one entry
|
// makes sure we always have at least one entry
|
||||||
nullptr,
|
nullptr,
|
||||||
|
|
|
@ -121,6 +121,10 @@ static LuauBytecodeType getType(
|
||||||
{
|
{
|
||||||
return LBC_TYPE_ANY;
|
return LBC_TYPE_ANY;
|
||||||
}
|
}
|
||||||
|
else if (const AstTypeGroup* group = ty->as<AstTypeGroup>())
|
||||||
|
{
|
||||||
|
return getType(group->type, generics, typeAliases, resolveAliases, hostVectorType, userdataTypes, bytecode);
|
||||||
|
}
|
||||||
|
|
||||||
return LBC_TYPE_ANY;
|
return LBC_TYPE_ANY;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ target_sources(Luau.Ast PRIVATE
|
||||||
Ast/include/Luau/Allocator.h
|
Ast/include/Luau/Allocator.h
|
||||||
Ast/include/Luau/Ast.h
|
Ast/include/Luau/Ast.h
|
||||||
Ast/include/Luau/Confusables.h
|
Ast/include/Luau/Confusables.h
|
||||||
|
Ast/include/Luau/Cst.h
|
||||||
Ast/include/Luau/Lexer.h
|
Ast/include/Luau/Lexer.h
|
||||||
Ast/include/Luau/Location.h
|
Ast/include/Luau/Location.h
|
||||||
Ast/include/Luau/ParseOptions.h
|
Ast/include/Luau/ParseOptions.h
|
||||||
|
@ -28,6 +29,7 @@ target_sources(Luau.Ast PRIVATE
|
||||||
Ast/src/Allocator.cpp
|
Ast/src/Allocator.cpp
|
||||||
Ast/src/Ast.cpp
|
Ast/src/Ast.cpp
|
||||||
Ast/src/Confusables.cpp
|
Ast/src/Confusables.cpp
|
||||||
|
Ast/src/Cst.cpp
|
||||||
Ast/src/Lexer.cpp
|
Ast/src/Lexer.cpp
|
||||||
Ast/src/Location.cpp
|
Ast/src/Location.cpp
|
||||||
Ast/src/Parser.cpp
|
Ast/src/Parser.cpp
|
||||||
|
|
|
@ -270,7 +270,7 @@ static int buffer_readbits(lua_State* L)
|
||||||
|
|
||||||
uint64_t data = 0;
|
uint64_t data = 0;
|
||||||
|
|
||||||
#if LUAU_BIG_ENDIAN
|
#if defined(LUAU_BIG_ENDIAN)
|
||||||
for (int i = int(endbyte) - 1; i >= int(startbyte); i--)
|
for (int i = int(endbyte) - 1; i >= int(startbyte); i--)
|
||||||
data = (data << 8) + uint8_t(((char*)buf)[i]);
|
data = (data << 8) + uint8_t(((char*)buf)[i]);
|
||||||
#else
|
#else
|
||||||
|
@ -306,7 +306,7 @@ static int buffer_writebits(lua_State* L)
|
||||||
|
|
||||||
uint64_t data = 0;
|
uint64_t data = 0;
|
||||||
|
|
||||||
#if LUAU_BIG_ENDIAN
|
#if defined(LUAU_BIG_ENDIAN)
|
||||||
for (int i = int(endbyte) - 1; i >= int(startbyte); i--)
|
for (int i = int(endbyte) - 1; i >= int(startbyte); i--)
|
||||||
data = data * 256 + uint8_t(((char*)buf)[i]);
|
data = data * 256 + uint8_t(((char*)buf)[i]);
|
||||||
#else
|
#else
|
||||||
|
@ -318,7 +318,7 @@ static int buffer_writebits(lua_State* L)
|
||||||
|
|
||||||
data = (data & ~mask) | ((uint64_t(value) << subbyteoffset) & mask);
|
data = (data & ~mask) | ((uint64_t(value) << subbyteoffset) & mask);
|
||||||
|
|
||||||
#if LUAU_BIG_ENDIAN
|
#if defined(LUAU_BIG_ENDIAN)
|
||||||
for (int i = int(startbyte); i < int(endbyte); i++)
|
for (int i = int(startbyte); i < int(endbyte); i++)
|
||||||
{
|
{
|
||||||
((char*)buf)[i] = data & 0xff;
|
((char*)buf)[i] = data & 0xff;
|
||||||
|
|
|
@ -20,7 +20,8 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
LUAU_FASTFLAG(StudioReportLuauAny2)
|
||||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||||
LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes)
|
LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes)
|
||||||
LUAU_FASTFLAG(LuauRemoveNotAnyHack)
|
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||||
|
LUAU_FASTFLAG(LuauAstTypeGroup)
|
||||||
|
|
||||||
|
|
||||||
struct ATSFixture : BuiltinsFixture
|
struct ATSFixture : BuiltinsFixture
|
||||||
|
@ -74,7 +75,22 @@ export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))");
|
if (FFlag::LuauStoreCSTData && FFlag::LuauAstTypeGroup)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0...>( true | any)->(''))");
|
||||||
|
}
|
||||||
|
else if (FFlag::LuauStoreCSTData)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0...>( true | any)->(''))");
|
||||||
|
}
|
||||||
|
else if (FFlag::LuauAstTypeGroup)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0 ...>(true | any)->(''))");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "typepacks")
|
TEST_CASE_FIXTURE(ATSFixture, "typepacks")
|
||||||
|
@ -413,7 +429,6 @@ TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable")
|
||||||
{FFlag::LuauSolverV2, true},
|
{FFlag::LuauSolverV2, true},
|
||||||
{FFlag::StudioReportLuauAny2, true},
|
{FFlag::StudioReportLuauAny2, true},
|
||||||
{FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true},
|
{FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true},
|
||||||
{FFlag::LuauRemoveNotAnyHack, true},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||||
|
@ -507,7 +522,6 @@ TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
|
||||||
{FFlag::LuauSolverV2, true},
|
{FFlag::LuauSolverV2, true},
|
||||||
{FFlag::StudioReportLuauAny2, true},
|
{FFlag::StudioReportLuauAny2, true},
|
||||||
{FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true},
|
{FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true},
|
||||||
{FFlag::LuauRemoveNotAnyHack, true},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||||
|
@ -577,13 +591,26 @@ initialize()
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
|
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||||
LUAU_ASSERT(
|
if (FFlag::LuauStoreCSTData)
|
||||||
module->ats.typeInfo[0].node ==
|
{
|
||||||
"local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if "
|
CHECK_EQ(
|
||||||
"descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in "
|
module->ats.typeInfo[0].node,
|
||||||
"character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n "
|
"local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if "
|
||||||
"end\nend"
|
"descendant:IsA('BasePart') then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in "
|
||||||
);
|
"character:GetDescendants() do\n if descendant:IsA('BasePart') then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n "
|
||||||
|
"end\nend"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(
|
||||||
|
module->ats.typeInfo[0].node ==
|
||||||
|
"local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if "
|
||||||
|
"descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in "
|
||||||
|
"character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n "
|
||||||
|
"end\nend"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1")
|
TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1")
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauAstTypeGroup)
|
||||||
|
|
||||||
struct JsonEncoderFixture
|
struct JsonEncoderFixture
|
||||||
{
|
{
|
||||||
Allocator allocator;
|
Allocator allocator;
|
||||||
|
@ -471,10 +473,17 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
|
||||||
{
|
{
|
||||||
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
|
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
|
||||||
|
|
||||||
std::string_view expected =
|
if (FFlag::LuauAstTypeGroup)
|
||||||
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}]},"exported":false})";
|
{
|
||||||
|
std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeGroup","location":"0,9 - 0,36","type":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","type":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,55","type":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})";
|
||||||
CHECK(toJson(statement) == expected);
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string_view expected =
|
||||||
|
R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}]},"exported":false})";
|
||||||
|
CHECK(toJson(statement) == expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_type_literal")
|
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_type_literal")
|
||||||
|
|
|
@ -4329,4 +4329,21 @@ local var = data;@1
|
||||||
CHECK_EQ(ac.context, AutocompleteContext::Statement);
|
CHECK_EQ(ac.context, AutocompleteContext::Statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(ACBuiltinsFixture, "require_tracing")
|
||||||
|
{
|
||||||
|
fileResolver.source["Module/A"] = R"(
|
||||||
|
return { x = 0 }
|
||||||
|
)";
|
||||||
|
|
||||||
|
fileResolver.source["Module/B"] = R"(
|
||||||
|
local result = require(script.Parent.A)
|
||||||
|
local x = 1 + result.
|
||||||
|
)";
|
||||||
|
|
||||||
|
auto ac = autocomplete("Module/B", Position{2, 21});
|
||||||
|
|
||||||
|
CHECK(ac.entryMap.size() == 1);
|
||||||
|
CHECK(ac.entryMap.count("x"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -8757,6 +8757,23 @@ end
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("TypeGroup")
|
||||||
|
{
|
||||||
|
CHECK_EQ(
|
||||||
|
"\n" + compileTypeTable(R"(
|
||||||
|
function myfunc(test: (string), foo: nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function myfunc2(test: (string | nil), foo: nil)
|
||||||
|
end
|
||||||
|
)"),
|
||||||
|
R"(
|
||||||
|
0: function(string, nil)
|
||||||
|
1: function(string?, nil)
|
||||||
|
)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("BuiltinFoldMathK")
|
TEST_CASE("BuiltinFoldMathK")
|
||||||
{
|
{
|
||||||
// we can fold math.pi at optimization level 2
|
// we can fold math.pi at optimization level 2
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "Fixture.h"
|
#include "Fixture.h"
|
||||||
|
|
||||||
#include "Luau/EqSatSimplification.h"
|
#include "Luau/EqSatSimplification.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ TEST_CASE_FIXTURE(ESFixture, "number | string")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ESFixture, "t1 where t1 = number | t1")
|
TEST_CASE_FIXTURE(ESFixture, "t1 where t1 = number | t1")
|
||||||
{
|
{
|
||||||
TypeId ty = arena->freshType(nullptr);
|
TypeId ty = arena->freshType(builtinTypes, nullptr);
|
||||||
asMutable(ty)->ty.emplace<UnionType>(std::vector<TypeId>{builtinTypes->numberType, ty});
|
asMutable(ty)->ty.emplace<UnionType>(std::vector<TypeId>{builtinTypes->numberType, ty});
|
||||||
|
|
||||||
CHECK("number" == simplifyStr(ty));
|
CHECK("number" == simplifyStr(ty));
|
||||||
|
@ -450,7 +451,7 @@ TEST_CASE_FIXTURE(ESFixture, "(boolean | nil) & (false | nil)")
|
||||||
TEST_CASE_FIXTURE(ESFixture, "free & string & number")
|
TEST_CASE_FIXTURE(ESFixture, "free & string & number")
|
||||||
{
|
{
|
||||||
Scope scope{builtinTypes->anyTypePack};
|
Scope scope{builtinTypes->anyTypePack};
|
||||||
const TypeId freeTy = arena->addType(FreeType{&scope});
|
const TypeId freeTy = arena->freshType(builtinTypes, &scope);
|
||||||
|
|
||||||
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{freeTy, builtinTypes->numberType, builtinTypes->stringType}})));
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{freeTy, builtinTypes->numberType, builtinTypes->stringType}})));
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
#include "Luau/AutocompleteTypes.h"
|
#include "Luau/AutocompleteTypes.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
@ -27,6 +28,14 @@ LUAU_FASTFLAG(LuauStoreSolverTypeOnModule);
|
||||||
LUAU_FASTFLAG(LexerResumesFromPosition2)
|
LUAU_FASTFLAG(LexerResumesFromPosition2)
|
||||||
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
|
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
|
||||||
LUAU_FASTINT(LuauParseErrorLimit)
|
LUAU_FASTINT(LuauParseErrorLimit)
|
||||||
|
LUAU_FASTFLAG(LuauCloneIncrementalModule)
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauIncrementalAutocompleteBugfixes)
|
||||||
|
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver)
|
||||||
|
LUAU_FASTFLAG(LuauMixedModeDefFinderTraversesTypeOf)
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
|
||||||
|
|
||||||
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
|
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
|
||||||
{
|
{
|
||||||
|
@ -46,15 +55,25 @@ static FrontendOptions getOptions()
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ModuleResolver& getModuleResolver(Luau::Frontend& frontend)
|
||||||
|
{
|
||||||
|
return FFlag::LuauSolverV2 ? frontend.moduleResolver : frontend.moduleResolverForAutocomplete;
|
||||||
|
}
|
||||||
|
|
||||||
template<class BaseType>
|
template<class BaseType>
|
||||||
struct FragmentAutocompleteFixtureImpl : BaseType
|
struct FragmentAutocompleteFixtureImpl : BaseType
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs[5] = {
|
static_assert(std::is_base_of_v<Fixture, BaseType>, "BaseType must be a descendant of Fixture");
|
||||||
|
|
||||||
|
ScopedFastFlag sffs[8] = {
|
||||||
{FFlag::LuauAllowFragmentParsing, true},
|
{FFlag::LuauAllowFragmentParsing, true},
|
||||||
{FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true},
|
{FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true},
|
||||||
{FFlag::LuauStoreSolverTypeOnModule, true},
|
{FFlag::LuauStoreSolverTypeOnModule, true},
|
||||||
{FFlag::LuauSymbolEquality, true},
|
{FFlag::LuauSymbolEquality, true},
|
||||||
{FFlag::LexerResumesFromPosition2, true}
|
{FFlag::LexerResumesFromPosition2, true},
|
||||||
|
{FFlag::LuauReferenceAllocatorInNewSolver, true},
|
||||||
|
{FFlag::LuauIncrementalAutocompleteBugfixes, true},
|
||||||
|
{FFlag::LuauBetterReverseDependencyTracking, true},
|
||||||
};
|
};
|
||||||
|
|
||||||
FragmentAutocompleteFixtureImpl()
|
FragmentAutocompleteFixtureImpl()
|
||||||
|
@ -128,6 +147,26 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
||||||
result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||||
assertions(result);
|
assertions(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragmentForModule(
|
||||||
|
const ModuleName& module,
|
||||||
|
const std::string& document,
|
||||||
|
Position cursorPos,
|
||||||
|
std::optional<Position> fragmentEndPosition = std::nullopt
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Luau::typecheckFragment(this->frontend, module, cursorPos, getOptions(), document, fragmentEndPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
FragmentAutocompleteResult autocompleteFragmentForModule(
|
||||||
|
const ModuleName& module,
|
||||||
|
const std::string& document,
|
||||||
|
Position cursorPos,
|
||||||
|
std::optional<Position> fragmentEndPosition = std::nullopt
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Luau::fragmentAutocomplete(this->frontend, document, module, cursorPos, getOptions(), nullCallback, fragmentEndPosition);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FragmentAutocompleteFixture : FragmentAutocompleteFixtureImpl<Fixture>
|
struct FragmentAutocompleteFixture : FragmentAutocompleteFixtureImpl<Fixture>
|
||||||
|
@ -162,10 +201,13 @@ end
|
||||||
// 'for autocomplete'.
|
// 'for autocomplete'.
|
||||||
loadDefinition(fakeVecDecl);
|
loadDefinition(fakeVecDecl);
|
||||||
loadDefinition(fakeVecDecl, /* For Autocomplete Module */ true);
|
loadDefinition(fakeVecDecl, /* For Autocomplete Module */ true);
|
||||||
|
|
||||||
|
addGlobalBinding(frontend.globals, "game", Binding{builtinTypes->anyType});
|
||||||
|
addGlobalBinding(frontend.globalsForAutocomplete, "game", Binding{builtinTypes->anyType});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//NOLINTBEGIN(bugprone-unchecked-optional-access)
|
// NOLINTBEGIN(bugprone-unchecked-optional-access)
|
||||||
TEST_SUITE_BEGIN("FragmentAutocompleteTraversalTests");
|
TEST_SUITE_BEGIN("FragmentAutocompleteTraversalTests");
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "just_two_locals")
|
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "just_two_locals")
|
||||||
|
@ -574,7 +616,7 @@ t
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
//NOLINTEND(bugprone-unchecked-optional-access)
|
// NOLINTEND(bugprone-unchecked-optional-access)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("FragmentAutocompleteTypeCheckerTests");
|
TEST_SUITE_BEGIN("FragmentAutocompleteTypeCheckerTests");
|
||||||
|
|
||||||
|
@ -710,6 +752,57 @@ tbl.
|
||||||
CHECK_EQ(AutocompleteContext::Property, fragment.acResults.context);
|
CHECK_EQ(AutocompleteContext::Property, fragment.acResults.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "typecheck_fragment_handles_stale_module")
|
||||||
|
{
|
||||||
|
const std::string sourceName = "MainModule";
|
||||||
|
fileResolver.source[sourceName] = "local x = 5";
|
||||||
|
|
||||||
|
CheckResult checkResult = frontend.check(sourceName, getOptions());
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(checkResult);
|
||||||
|
|
||||||
|
auto [result, _] = typecheckFragmentForModule(sourceName, fileResolver.source[sourceName], Luau::Position(0, 0));
|
||||||
|
CHECK_EQ(result, FragmentTypeCheckStatus::Success);
|
||||||
|
|
||||||
|
frontend.markDirty(sourceName);
|
||||||
|
frontend.parse(sourceName);
|
||||||
|
|
||||||
|
CHECK_NE(frontend.getSourceModule(sourceName), nullptr);
|
||||||
|
|
||||||
|
auto [result2, __] = typecheckFragmentForModule(sourceName, fileResolver.source[sourceName], Luau::Position(0, 0));
|
||||||
|
CHECK_EQ(result2, FragmentTypeCheckStatus::SkipAutocomplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "typecheck_fragment_handles_unusable_module")
|
||||||
|
{
|
||||||
|
const std::string sourceA = "MainModule";
|
||||||
|
fileResolver.source[sourceA] = R"(
|
||||||
|
local Modules = game:GetService('Gui').Modules
|
||||||
|
local B = require(Modules.B)
|
||||||
|
return { hello = B }
|
||||||
|
)";
|
||||||
|
|
||||||
|
const std::string sourceB = "game/Gui/Modules/B";
|
||||||
|
fileResolver.source[sourceB] = R"(return {hello = "hello"})";
|
||||||
|
|
||||||
|
CheckResult result = frontend.check(sourceA, getOptions());
|
||||||
|
CHECK(!frontend.isDirty(sourceA, getOptions().forAutocomplete));
|
||||||
|
|
||||||
|
std::weak_ptr<Module> weakModule = getModuleResolver(frontend).getModule(sourceB);
|
||||||
|
REQUIRE(!weakModule.expired());
|
||||||
|
|
||||||
|
frontend.markDirty(sourceB);
|
||||||
|
CHECK(frontend.isDirty(sourceA, getOptions().forAutocomplete));
|
||||||
|
|
||||||
|
frontend.check(sourceB, getOptions());
|
||||||
|
CHECK(weakModule.expired());
|
||||||
|
|
||||||
|
auto [status, _] = typecheckFragmentForModule(sourceA, fileResolver.source[sourceA], Luau::Position(0, 0));
|
||||||
|
CHECK_EQ(status, FragmentTypeCheckStatus::SkipAutocomplete);
|
||||||
|
|
||||||
|
auto [status2, _2] = typecheckFragmentForModule(sourceB, fileResolver.source[sourceB], Luau::Position(3, 20));
|
||||||
|
CHECK_EQ(status2, FragmentTypeCheckStatus::Success);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("FragmentAutocompleteTests");
|
TEST_SUITE_BEGIN("FragmentAutocompleteTests");
|
||||||
|
@ -1677,4 +1770,187 @@ type A = <>random non code text here
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_handles_stale_module")
|
||||||
|
{
|
||||||
|
const std::string sourceName = "MainModule";
|
||||||
|
fileResolver.source[sourceName] = "local x = 5";
|
||||||
|
|
||||||
|
frontend.check(sourceName, getOptions());
|
||||||
|
frontend.markDirty(sourceName);
|
||||||
|
frontend.parse(sourceName);
|
||||||
|
|
||||||
|
FragmentAutocompleteResult result = autocompleteFragmentForModule(sourceName, fileResolver.source[sourceName], Luau::Position(0, 0));
|
||||||
|
CHECK(result.acResults.entryMap.empty());
|
||||||
|
CHECK_EQ(result.incrementalModule, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "require_tracing")
|
||||||
|
{
|
||||||
|
fileResolver.source["MainModule/A"] = R"(
|
||||||
|
return { x = 0 }
|
||||||
|
)";
|
||||||
|
|
||||||
|
fileResolver.source["MainModule"] = R"(
|
||||||
|
local result = require(script.A)
|
||||||
|
local x = 1 + result.
|
||||||
|
)";
|
||||||
|
|
||||||
|
autocompleteFragmentInBothSolvers(
|
||||||
|
fileResolver.source["MainModule"],
|
||||||
|
fileResolver.source["MainModule"],
|
||||||
|
Position{2, 21},
|
||||||
|
[](FragmentAutocompleteResult& result)
|
||||||
|
{
|
||||||
|
CHECK(result.acResults.entryMap.size() == 1);
|
||||||
|
CHECK(result.acResults.entryMap.count("x"));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "fragment_ac_must_traverse_typeof_and_not_ice")
|
||||||
|
{
|
||||||
|
// This test ensures that we traverse typeof expressions for defs that are being referred to in the fragment
|
||||||
|
// In this case, we want to ensure we populate the incremental environment with the reference to `m`
|
||||||
|
// Without this, we would ice as we will refer to the local `m` before it's declaration
|
||||||
|
ScopedFastFlag sff{FFlag::LuauMixedModeDefFinderTraversesTypeOf, true};
|
||||||
|
const std::string source = R"(
|
||||||
|
--!strict
|
||||||
|
local m = {}
|
||||||
|
-- and here
|
||||||
|
function m:m1() end
|
||||||
|
type nt = typeof(m)
|
||||||
|
|
||||||
|
return m
|
||||||
|
)";
|
||||||
|
const std::string updated = R"(
|
||||||
|
--!strict
|
||||||
|
local m = {}
|
||||||
|
-- and here
|
||||||
|
function m:m1() end
|
||||||
|
type nt = typeof(m)
|
||||||
|
l
|
||||||
|
return m
|
||||||
|
)";
|
||||||
|
|
||||||
|
autocompleteFragmentInBothSolvers(source, updated, Position{6, 2}, [](auto& _) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "generalization_crash_when_old_solver_freetypes_have_no_bounds_set")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::LuauFreeTypesMustHaveBounds, true};
|
||||||
|
const std::string source = R"(
|
||||||
|
local UserInputService = game:GetService("UserInputService");
|
||||||
|
|
||||||
|
local Camera = workspace.CurrentCamera;
|
||||||
|
|
||||||
|
UserInputService.InputBegan:Connect(function(Input)
|
||||||
|
if (Input.KeyCode == Enum.KeyCode.One) then
|
||||||
|
local Up = Input.Foo
|
||||||
|
local Vector = -(Up:Unit)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
)";
|
||||||
|
|
||||||
|
const std::string dest = R"(
|
||||||
|
local UserInputService = game:GetService("UserInputService");
|
||||||
|
|
||||||
|
local Camera = workspace.CurrentCamera;
|
||||||
|
|
||||||
|
UserInputService.InputBegan:Connect(function(Input)
|
||||||
|
if (Input.KeyCode == Enum.KeyCode.One) then
|
||||||
|
local Up = Input.Foo
|
||||||
|
local Vector = -(Up:Unit())
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
)";
|
||||||
|
|
||||||
|
autocompleteFragmentInBothSolvers(source, dest, Position{8, 36}, [](auto& _) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::LuauCloneIncrementalModule, true};
|
||||||
|
ToStringOptions opt;
|
||||||
|
opt.exhaustive = true;
|
||||||
|
opt.exhaustive = true;
|
||||||
|
opt.functionTypeArguments = true;
|
||||||
|
opt.maxTableLength = 0;
|
||||||
|
opt.maxTypeLength = 0;
|
||||||
|
|
||||||
|
auto checkAndExamine = [&](const std::string& src, const std::string& idName, const std::string& idString)
|
||||||
|
{
|
||||||
|
check(src, getOptions());
|
||||||
|
auto id = getType(idName, true);
|
||||||
|
LUAU_ASSERT(id);
|
||||||
|
CHECK_EQ(Luau::toString(*id, opt), idString);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto getTypeFromModule = [](ModulePtr module, const std::string& name) -> std::optional<TypeId>
|
||||||
|
{
|
||||||
|
if (!module->hasModuleScope())
|
||||||
|
return std::nullopt;
|
||||||
|
return lookupName(module->getModuleScope(), name);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto fragmentACAndCheck = [&](const std::string& updated, const Position& pos, const std::string& idName)
|
||||||
|
{
|
||||||
|
FragmentAutocompleteResult result = autocompleteFragment(updated, pos, std::nullopt);
|
||||||
|
auto fragId = getTypeFromModule(result.incrementalModule, idName);
|
||||||
|
LUAU_ASSERT(fragId);
|
||||||
|
|
||||||
|
auto srcId = getType(idName, true);
|
||||||
|
LUAU_ASSERT(srcId);
|
||||||
|
|
||||||
|
CHECK((*fragId)->owningArena != (*srcId)->owningArena);
|
||||||
|
CHECK(&(result.incrementalModule->internalTypes) == (*fragId)->owningArena);
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string source = R"(local module = {}
|
||||||
|
f
|
||||||
|
return module)";
|
||||||
|
|
||||||
|
const std::string updated1 = R"(local module = {}
|
||||||
|
function module.a
|
||||||
|
return module)";
|
||||||
|
|
||||||
|
const std::string updated2 = R"(local module = {}
|
||||||
|
function module.ab
|
||||||
|
return module)";
|
||||||
|
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||||
|
checkAndExamine(source, "module", "{ }");
|
||||||
|
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
|
||||||
|
// early return since the following checking will fail, which it shouldn't!
|
||||||
|
fragmentACAndCheck(updated1, Position{1, 17}, "module");
|
||||||
|
fragmentACAndCheck(updated2, Position{1, 18}, "module");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||||
|
checkAndExamine(source, "module", "{ }");
|
||||||
|
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
|
||||||
|
// early return since the following checking will fail, which it shouldn't!
|
||||||
|
fragmentACAndCheck(updated1, Position{1, 17}, "module");
|
||||||
|
fragmentACAndCheck(updated2, Position{1, 18}, "module");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_shouldnt_crash_on_cross_module_mutation")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::LuauCloneIncrementalModule, true};
|
||||||
|
const std::string source = R"(local module = {}
|
||||||
|
function module.
|
||||||
|
return module
|
||||||
|
)";
|
||||||
|
|
||||||
|
const std::string updated = R"(local module = {}
|
||||||
|
function module.f
|
||||||
|
return module
|
||||||
|
)";
|
||||||
|
|
||||||
|
autocompleteFragmentInBothSolvers(source, updated, Position{1, 18}, [](FragmentAutocompleteResult& result) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/AstQuery.h"
|
#include "Luau/AstQuery.h"
|
||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
#include "Luau/RequireTracer.h"
|
#include "Luau/RequireTracer.h"
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||||
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver);
|
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver);
|
||||||
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
|
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
|
||||||
|
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking);
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -1572,4 +1574,207 @@ return {x = a, y = b, z = c}
|
||||||
CHECK(mod->keyArena.allocator.empty());
|
CHECK(mod->keyArena.allocator.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FrontendFixture, "test_traverse_dependents")
|
||||||
|
{
|
||||||
|
ScopedFastFlag dependencyTracking{FFlag::LuauBetterReverseDependencyTracking, true};
|
||||||
|
|
||||||
|
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
|
||||||
|
fileResolver.source["game/Gui/Modules/B"] = R"(
|
||||||
|
return require(game:GetService('Gui').Modules.A)
|
||||||
|
)";
|
||||||
|
fileResolver.source["game/Gui/Modules/C"] = R"(
|
||||||
|
local Modules = game:GetService('Gui').Modules
|
||||||
|
local B = require(Modules.B)
|
||||||
|
return {c_value = B.hello}
|
||||||
|
)";
|
||||||
|
fileResolver.source["game/Gui/Modules/D"] = R"(
|
||||||
|
local Modules = game:GetService('Gui').Modules
|
||||||
|
local C = require(Modules.C)
|
||||||
|
return {d_value = C.c_value}
|
||||||
|
)";
|
||||||
|
|
||||||
|
frontend.check("game/Gui/Modules/D");
|
||||||
|
|
||||||
|
std::vector<ModuleName> visited;
|
||||||
|
frontend.traverseDependents(
|
||||||
|
"game/Gui/Modules/B",
|
||||||
|
[&visited](SourceNode& node)
|
||||||
|
{
|
||||||
|
visited.push_back(node.name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK_EQ(std::vector<ModuleName>{"game/Gui/Modules/B", "game/Gui/Modules/C", "game/Gui/Modules/D"}, visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FrontendFixture, "test_traverse_dependents_early_exit")
|
||||||
|
{
|
||||||
|
ScopedFastFlag dependencyTracking{FFlag::LuauBetterReverseDependencyTracking, true};
|
||||||
|
|
||||||
|
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
|
||||||
|
fileResolver.source["game/Gui/Modules/B"] = R"(
|
||||||
|
return require(game:GetService('Gui').Modules.A)
|
||||||
|
)";
|
||||||
|
fileResolver.source["game/Gui/Modules/C"] = R"(
|
||||||
|
local Modules = game:GetService('Gui').Modules
|
||||||
|
local B = require(Modules.B)
|
||||||
|
return {c_value = B.hello}
|
||||||
|
)";
|
||||||
|
|
||||||
|
frontend.check("game/Gui/Modules/C");
|
||||||
|
|
||||||
|
std::vector<ModuleName> visited;
|
||||||
|
frontend.traverseDependents(
|
||||||
|
"game/Gui/Modules/A",
|
||||||
|
[&visited](SourceNode& node)
|
||||||
|
{
|
||||||
|
visited.push_back(node.name);
|
||||||
|
return node.name != "game/Gui/Modules/B";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK_EQ(std::vector<ModuleName>{"game/Gui/Modules/A", "game/Gui/Modules/B"}, visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FrontendFixture, "test_dependents_stored_on_node_as_graph_updates")
|
||||||
|
{
|
||||||
|
ScopedFastFlag dependencyTracking{FFlag::LuauBetterReverseDependencyTracking, true};
|
||||||
|
|
||||||
|
auto updateSource = [&](const std::string& name, const std::string& source)
|
||||||
|
{
|
||||||
|
fileResolver.source[name] = source;
|
||||||
|
frontend.markDirty(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto validateMatchesRequireLists = [&](const std::string& message)
|
||||||
|
{
|
||||||
|
DenseHashMap<ModuleName, std::vector<ModuleName>> dependents{{}};
|
||||||
|
for (const auto& module : frontend.sourceNodes)
|
||||||
|
{
|
||||||
|
for (const auto& dep : module.second->requireSet)
|
||||||
|
dependents[dep].push_back(module.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& module : frontend.sourceNodes)
|
||||||
|
{
|
||||||
|
Set<ModuleName>& dependentsForModule = module.second->dependents;
|
||||||
|
for (const auto& dep : dependents[module.first])
|
||||||
|
CHECK_MESSAGE(1 == dependentsForModule.count(dep), "Mismatch in dependents for " << module.first << ": " << message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto validateSecondDependsOnFirst = [&](const std::string& from, const std::string& to, bool expected)
|
||||||
|
{
|
||||||
|
SourceNode& fromNode = *frontend.sourceNodes[from];
|
||||||
|
CHECK_MESSAGE(
|
||||||
|
fromNode.dependents.count(to) == int(expected),
|
||||||
|
"Expected " << from << " to " << (expected ? std::string() : std::string("not ")) << "have a reverse dependency on " << to
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// C -> B -> A
|
||||||
|
{
|
||||||
|
updateSource("game/Gui/Modules/A", "return {hello=5, world=true}");
|
||||||
|
updateSource("game/Gui/Modules/B", R"(
|
||||||
|
return require(game:GetService('Gui').Modules.A)
|
||||||
|
)");
|
||||||
|
updateSource("game/Gui/Modules/C", R"(
|
||||||
|
local Modules = game:GetService('Gui').Modules
|
||||||
|
local B = require(Modules.B)
|
||||||
|
return {c_value = B}
|
||||||
|
)");
|
||||||
|
frontend.check("game/Gui/Modules/C");
|
||||||
|
|
||||||
|
validateMatchesRequireLists("Initial check");
|
||||||
|
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/A", "game/Gui/Modules/B", true);
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/B", "game/Gui/Modules/C", true);
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/C", "game/Gui/Modules/A", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// C -> B, A
|
||||||
|
{
|
||||||
|
updateSource("game/Gui/Modules/B", R"(
|
||||||
|
return 1
|
||||||
|
)");
|
||||||
|
frontend.check("game/Gui/Modules/C");
|
||||||
|
|
||||||
|
validateMatchesRequireLists("Removing dependency B->A");
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/A", "game/Gui/Modules/B", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// C -> B -> A
|
||||||
|
{
|
||||||
|
updateSource("game/Gui/Modules/B", R"(
|
||||||
|
return require(game:GetService('Gui').Modules.A)
|
||||||
|
)");
|
||||||
|
frontend.check("game/Gui/Modules/C");
|
||||||
|
|
||||||
|
validateMatchesRequireLists("Adding back B->A");
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/A", "game/Gui/Modules/B", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// C -> B -> A, D -> (C,B,A)
|
||||||
|
{
|
||||||
|
updateSource("game/Gui/Modules/D", R"(
|
||||||
|
local C = require(game:GetService('Gui').Modules.C)
|
||||||
|
local B = require(game:GetService('Gui').Modules.B)
|
||||||
|
local A = require(game:GetService('Gui').Modules.A)
|
||||||
|
return {d_value = C.c_value}
|
||||||
|
)");
|
||||||
|
frontend.check("game/Gui/Modules/D");
|
||||||
|
|
||||||
|
validateMatchesRequireLists("Adding D->C, D->B, D->A");
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/A", "game/Gui/Modules/D", true);
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/B", "game/Gui/Modules/D", true);
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/C", "game/Gui/Modules/D", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// B -> A, C <-> D
|
||||||
|
{
|
||||||
|
updateSource("game/Gui/Modules/D", "return require(game:GetService('Gui').Modules.C)");
|
||||||
|
updateSource("game/Gui/Modules/C", "return require(game:GetService('Gui').Modules.D)");
|
||||||
|
frontend.check("game/Gui/Modules/D");
|
||||||
|
|
||||||
|
validateMatchesRequireLists("Adding cycle D->C, C->D");
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/C", "game/Gui/Modules/D", true);
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/D", "game/Gui/Modules/C", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// B -> A, C -> D, D -> error
|
||||||
|
{
|
||||||
|
updateSource("game/Gui/Modules/D", "return require(game:GetService('Gui').Modules.C.)");
|
||||||
|
frontend.check("game/Gui/Modules/D");
|
||||||
|
|
||||||
|
validateMatchesRequireLists("Adding error dependency D->C.");
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/D", "game/Gui/Modules/C", true);
|
||||||
|
validateSecondDependsOnFirst("game/Gui/Modules/C", "game/Gui/Modules/D", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_resolver")
|
||||||
|
{
|
||||||
|
ScopedFastFlag dependencyTracking{FFlag::LuauBetterReverseDependencyTracking, true};
|
||||||
|
ScopedFastFlag newSolver{FFlag::LuauSolverV2, false};
|
||||||
|
|
||||||
|
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
|
||||||
|
fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)";
|
||||||
|
|
||||||
|
FrontendOptions opts;
|
||||||
|
opts.forAutocomplete = false;
|
||||||
|
|
||||||
|
frontend.check("game/Gui/Modules/B", opts);
|
||||||
|
CHECK(frontend.allModuleDependenciesValid("game/Gui/Modules/B", opts.forAutocomplete));
|
||||||
|
CHECK(!frontend.allModuleDependenciesValid("game/Gui/Modules/B", !opts.forAutocomplete));
|
||||||
|
|
||||||
|
opts.forAutocomplete = true;
|
||||||
|
frontend.check("game/Gui/Modules/A", opts);
|
||||||
|
|
||||||
|
CHECK(!frontend.allModuleDependenciesValid("game/Gui/Modules/B", opts.forAutocomplete));
|
||||||
|
CHECK(frontend.allModuleDependenciesValid("game/Gui/Modules/B", !opts.forAutocomplete));
|
||||||
|
CHECK(frontend.allModuleDependenciesValid("game/Gui/Modules/A", !opts.forAutocomplete));
|
||||||
|
CHECK(frontend.allModuleDependenciesValid("game/Gui/Modules/A", opts.forAutocomplete));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -179,9 +179,9 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "functions_containing_cyclic_tables_can
|
||||||
TEST_CASE_FIXTURE(GeneralizationFixture, "union_type_traversal_doesnt_crash")
|
TEST_CASE_FIXTURE(GeneralizationFixture, "union_type_traversal_doesnt_crash")
|
||||||
{
|
{
|
||||||
// t1 where t1 = ('h <: (t1 <: 'i)) | ('j <: (t1 <: 'i))
|
// t1 where t1 = ('h <: (t1 <: 'i)) | ('j <: (t1 <: 'i))
|
||||||
TypeId i = arena.addType(FreeType{NotNull{globalScope.get()}});
|
TypeId i = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||||
TypeId h = arena.addType(FreeType{NotNull{globalScope.get()}});
|
TypeId h = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||||
TypeId j = arena.addType(FreeType{NotNull{globalScope.get()}});
|
TypeId j = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||||
TypeId unionType = arena.addType(UnionType{{h, j}});
|
TypeId unionType = arena.addType(UnionType{{h, j}});
|
||||||
getMutable<FreeType>(h)->upperBound = i;
|
getMutable<FreeType>(h)->upperBound = i;
|
||||||
getMutable<FreeType>(h)->lowerBound = builtinTypes.neverType;
|
getMutable<FreeType>(h)->lowerBound = builtinTypes.neverType;
|
||||||
|
@ -196,9 +196,9 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "union_type_traversal_doesnt_crash")
|
||||||
TEST_CASE_FIXTURE(GeneralizationFixture, "intersection_type_traversal_doesnt_crash")
|
TEST_CASE_FIXTURE(GeneralizationFixture, "intersection_type_traversal_doesnt_crash")
|
||||||
{
|
{
|
||||||
// t1 where t1 = ('h <: (t1 <: 'i)) & ('j <: (t1 <: 'i))
|
// t1 where t1 = ('h <: (t1 <: 'i)) & ('j <: (t1 <: 'i))
|
||||||
TypeId i = arena.addType(FreeType{NotNull{globalScope.get()}});
|
TypeId i = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||||
TypeId h = arena.addType(FreeType{NotNull{globalScope.get()}});
|
TypeId h = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||||
TypeId j = arena.addType(FreeType{NotNull{globalScope.get()}});
|
TypeId j = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||||
TypeId intersectionType = arena.addType(IntersectionType{{h, j}});
|
TypeId intersectionType = arena.addType(IntersectionType{{h, j}});
|
||||||
|
|
||||||
getMutable<FreeType>(h)->upperBound = i;
|
getMutable<FreeType>(h)->upperBound = i;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "Fixture.h"
|
#include "Fixture.h"
|
||||||
#include "ClassFixture.h"
|
#include "ClassFixture.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
#include "ScopedFlags.h"
|
#include "ScopedFlags.h"
|
||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
@ -29,7 +30,7 @@ TEST_CASE_FIXTURE(Fixture, "weird_cyclic_instantiation")
|
||||||
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
|
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
|
||||||
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
||||||
|
|
||||||
TypeId freeTy = arena.freshType(&scope);
|
TypeId freeTy = arena.freshType(builtinTypes, &scope);
|
||||||
FreeType* ft = getMutable<FreeType>(freeTy);
|
FreeType* ft = getMutable<FreeType>(freeTy);
|
||||||
REQUIRE(ft);
|
REQUIRE(ft);
|
||||||
ft->lowerBound = idTy;
|
ft->lowerBound = idTy;
|
||||||
|
|
|
@ -248,4 +248,185 @@ TEST_CASE("string_interpolation_with_unicode_escape")
|
||||||
CHECK_EQ(lexer.next().type, Lexeme::Eof);
|
CHECK_EQ(lexer.next().type, Lexeme::Eof);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("single_quoted_string")
|
||||||
|
{
|
||||||
|
const std::string testInput = "'test'";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
CHECK_EQ(lexeme.type, Lexeme::QuotedString);
|
||||||
|
CHECK_EQ(lexeme.getQuoteStyle(), Lexeme::QuoteStyle::Single);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("double_quoted_string")
|
||||||
|
{
|
||||||
|
const std::string testInput = R"("test")";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
CHECK_EQ(lexeme.type, Lexeme::QuotedString);
|
||||||
|
CHECK_EQ(lexeme.getQuoteStyle(), Lexeme::QuoteStyle::Double);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_0")
|
||||||
|
{
|
||||||
|
const std::string testInput = "[[ test ]]";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_0_multiline_1")
|
||||||
|
{
|
||||||
|
const std::string testInput = R"([[ test
|
||||||
|
]])";
|
||||||
|
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_0_multiline_2")
|
||||||
|
{
|
||||||
|
const std::string testInput = R"([[
|
||||||
|
test
|
||||||
|
]])";
|
||||||
|
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_0_multiline_3")
|
||||||
|
{
|
||||||
|
const std::string testInput = R"([[
|
||||||
|
test ]])";
|
||||||
|
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_1")
|
||||||
|
{
|
||||||
|
const std::string testInput = "[=[[%s]]=]";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_2")
|
||||||
|
{
|
||||||
|
const std::string testInput = "[==[ test ]==]";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_2_multiline_1")
|
||||||
|
{
|
||||||
|
const std::string testInput = R"([==[ test
|
||||||
|
]==])";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_2_multiline_2")
|
||||||
|
{
|
||||||
|
const std::string testInput = R"([==[
|
||||||
|
test
|
||||||
|
]==])";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_2_multiline_3")
|
||||||
|
{
|
||||||
|
const std::string testInput = R"([==[
|
||||||
|
|
||||||
|
test ]==])";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::RawString);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_comment_block_depth_0")
|
||||||
|
{
|
||||||
|
const std::string testInput = "--[[ test ]]";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::BlockComment);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_1")
|
||||||
|
{
|
||||||
|
const std::string testInput = "--[=[ μέλλον ]=]";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::BlockComment);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer_determines_string_block_depth_2")
|
||||||
|
{
|
||||||
|
const std::string testInput = "--[==[ test ]==]";
|
||||||
|
Luau::Allocator alloc;
|
||||||
|
AstNameTable table(alloc);
|
||||||
|
Lexer lexer(testInput.c_str(), testInput.size(), table);
|
||||||
|
|
||||||
|
Lexeme lexeme = lexer.next();
|
||||||
|
REQUIRE_EQ(lexeme.type, Lexeme::BlockComment);
|
||||||
|
CHECK_EQ(lexeme.getBlockDepth(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauNormalizationTracksCyclicPairsThroughInhabitance)
|
LUAU_FASTFLAG(LuauFixNormalizedIntersectionOfNegatedClass)
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -851,17 +851,17 @@ TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
|
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag _{FFlag::LuauFixNormalizedIntersectionOfNegatedClass, true};
|
||||||
createSomeClasses(&frontend);
|
createSomeClasses(&frontend);
|
||||||
CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not<Child>) | Unrelated")));
|
CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not<Child>) | Unrelated")));
|
||||||
CHECK("((class & ~Child) | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<Child>")));
|
CHECK("((class & ~Child) | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<Child>")));
|
||||||
CHECK("Child" == toString(normal("Not<Parent> & Child")));
|
CHECK("never" == toString(normal("Not<Parent> & Child")));
|
||||||
CHECK("((class & ~Parent) | Child | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<Parent> | Child")));
|
CHECK("((class & ~Parent) | Child | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<Parent> | Child")));
|
||||||
CHECK("(boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<cls>")));
|
CHECK("(boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<cls>")));
|
||||||
CHECK(
|
CHECK(
|
||||||
"(Parent | Unrelated | boolean | buffer | function | number | string | table | thread)?" ==
|
"(Parent | Unrelated | boolean | buffer | function | number | string | table | thread)?" ==
|
||||||
toString(normal("Not<cls & Not<Parent> & Not<Child> & Not<Unrelated>>"))
|
toString(normal("Not<cls & Not<Parent> & Not<Child> & Not<Unrelated>>"))
|
||||||
);
|
);
|
||||||
|
|
||||||
CHECK("Child" == toString(normal("(Child | Unrelated) & Not<Unrelated>")));
|
CHECK("Child" == toString(normal("(Child | Unrelated) & Not<Unrelated>")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -962,7 +962,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "final_types_are_cached")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "non_final_types_can_be_normalized_but_are_not_cached")
|
TEST_CASE_FIXTURE(NormalizeFixture, "non_final_types_can_be_normalized_but_are_not_cached")
|
||||||
{
|
{
|
||||||
TypeId a = arena.freshType(&globalScope);
|
TypeId a = arena.freshType(builtinTypes, &globalScope);
|
||||||
|
|
||||||
std::shared_ptr<const NormalizedType> na1 = normalizer.normalize(a);
|
std::shared_ptr<const NormalizedType> na1 = normalizer.normalize(a);
|
||||||
std::shared_ptr<const NormalizedType> na2 = normalizer.normalize(a);
|
std::shared_ptr<const NormalizedType> na2 = normalizer.normalize(a);
|
||||||
|
@ -1034,7 +1034,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_t
|
||||||
if (!FFlag::LuauSolverV2)
|
if (!FFlag::LuauSolverV2)
|
||||||
return;
|
return;
|
||||||
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
|
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
|
||||||
ScopedFastFlag sff{FFlag::LuauNormalizationTracksCyclicPairsThroughInhabitance, true};
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!strict
|
--!strict
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes)
|
||||||
LUAU_FASTFLAG(LuauErrorRecoveryForClassNames)
|
LUAU_FASTFLAG(LuauErrorRecoveryForClassNames)
|
||||||
LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
|
LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
|
||||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||||
|
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
LUAU_FASTFLAG(LuauAstTypeGroup)
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -369,7 +371,10 @@ TEST_CASE_FIXTURE(Fixture, "return_type_is_an_intersection_type_if_led_with_one_
|
||||||
|
|
||||||
AstTypeIntersection* returnAnnotation = annotation->returnTypes.types.data[0]->as<AstTypeIntersection>();
|
AstTypeIntersection* returnAnnotation = annotation->returnTypes.types.data[0]->as<AstTypeIntersection>();
|
||||||
REQUIRE(returnAnnotation != nullptr);
|
REQUIRE(returnAnnotation != nullptr);
|
||||||
CHECK(returnAnnotation->types.data[0]->as<AstTypeReference>());
|
if (FFlag::LuauAstTypeGroup)
|
||||||
|
CHECK(returnAnnotation->types.data[0]->as<AstTypeGroup>());
|
||||||
|
else
|
||||||
|
CHECK(returnAnnotation->types.data[0]->as<AstTypeReference>());
|
||||||
CHECK(returnAnnotation->types.data[1]->as<AstTypeFunction>());
|
CHECK(returnAnnotation->types.data[1]->as<AstTypeFunction>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2418,6 +2423,91 @@ TEST_CASE_FIXTURE(Fixture, "invalid_user_defined_type_functions")
|
||||||
matchParseError("type function foo() local v1 = 1; type function bar() print(v1) end end", "Type function cannot reference outer local 'v1'");
|
matchParseError("type function foo() local v1 = 1; type function bar() print(v1) end end", "Type function cannot reference outer local 'v1'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "leading_union_intersection_with_single_type_preserves_the_union_intersection_ast_node")
|
||||||
|
{
|
||||||
|
ScopedFastFlag _{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true};
|
||||||
|
AstStatBlock* block = parse(R"(
|
||||||
|
type Foo = | string
|
||||||
|
type Bar = & number
|
||||||
|
)");
|
||||||
|
|
||||||
|
REQUIRE_EQ(2, block->body.size);
|
||||||
|
|
||||||
|
const auto alias1 = block->body.data[0]->as<AstStatTypeAlias>();
|
||||||
|
REQUIRE(alias1);
|
||||||
|
|
||||||
|
const auto unionType = alias1->type->as<AstTypeUnion>();
|
||||||
|
REQUIRE(unionType);
|
||||||
|
CHECK_EQ(1, unionType->types.size);
|
||||||
|
|
||||||
|
const auto alias2 = block->body.data[1]->as<AstStatTypeAlias>();
|
||||||
|
REQUIRE(alias2);
|
||||||
|
|
||||||
|
const auto intersectionType = alias2->type->as<AstTypeIntersection>();
|
||||||
|
REQUIRE(intersectionType);
|
||||||
|
CHECK_EQ(1, intersectionType->types.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "parse_simple_ast_type_group")
|
||||||
|
{
|
||||||
|
ScopedFastFlag _{FFlag::LuauAstTypeGroup, true};
|
||||||
|
|
||||||
|
AstStatBlock* stat = parse(R"(
|
||||||
|
type Foo = (string)
|
||||||
|
)");
|
||||||
|
REQUIRE(stat);
|
||||||
|
REQUIRE_EQ(1, stat->body.size);
|
||||||
|
|
||||||
|
auto alias1 = stat->body.data[0]->as<AstStatTypeAlias>();
|
||||||
|
REQUIRE(alias1);
|
||||||
|
|
||||||
|
auto group1 = alias1->type->as<AstTypeGroup>();
|
||||||
|
REQUIRE(group1);
|
||||||
|
CHECK(group1->type->is<AstTypeReference>());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "parse_nested_ast_type_group")
|
||||||
|
{
|
||||||
|
ScopedFastFlag _{FFlag::LuauAstTypeGroup, true};
|
||||||
|
|
||||||
|
AstStatBlock* stat = parse(R"(
|
||||||
|
type Foo = ((string))
|
||||||
|
)");
|
||||||
|
REQUIRE(stat);
|
||||||
|
REQUIRE_EQ(1, stat->body.size);
|
||||||
|
|
||||||
|
auto alias1 = stat->body.data[0]->as<AstStatTypeAlias>();
|
||||||
|
REQUIRE(alias1);
|
||||||
|
|
||||||
|
auto group1 = alias1->type->as<AstTypeGroup>();
|
||||||
|
REQUIRE(group1);
|
||||||
|
|
||||||
|
auto group2 = group1->type->as<AstTypeGroup>();
|
||||||
|
REQUIRE(group2);
|
||||||
|
CHECK(group2->type->is<AstTypeReference>());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group")
|
||||||
|
{
|
||||||
|
ScopedFastFlag _{FFlag::LuauAstTypeGroup, true};
|
||||||
|
|
||||||
|
AstStatBlock* stat = parse(R"(
|
||||||
|
type Foo = () -> (string)
|
||||||
|
)");
|
||||||
|
REQUIRE(stat);
|
||||||
|
REQUIRE_EQ(1, stat->body.size);
|
||||||
|
|
||||||
|
auto alias1 = stat->body.data[0]->as<AstStatTypeAlias>();
|
||||||
|
REQUIRE(alias1);
|
||||||
|
|
||||||
|
auto funcType = alias1->type->as<AstTypeFunction>();
|
||||||
|
REQUIRE(funcType);
|
||||||
|
|
||||||
|
REQUIRE_EQ(1, funcType->returnTypes.types.size);
|
||||||
|
REQUIRE(!funcType->returnTypes.tailType);
|
||||||
|
CHECK(funcType->returnTypes.types.data[0]->is<AstTypeGroup>());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("ParseErrorRecovery");
|
TEST_SUITE_BEGIN("ParseErrorRecovery");
|
||||||
|
@ -3688,7 +3778,14 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
|
||||||
auto unionTy = paramTy.type->as<AstTypeUnion>();
|
auto unionTy = paramTy.type->as<AstTypeUnion>();
|
||||||
LUAU_ASSERT(unionTy);
|
LUAU_ASSERT(unionTy);
|
||||||
CHECK_EQ(unionTy->types.size, 2);
|
CHECK_EQ(unionTy->types.size, 2);
|
||||||
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
|
if (FFlag::LuauAstTypeGroup)
|
||||||
|
{
|
||||||
|
auto groupTy = unionTy->types.data[0]->as<AstTypeGroup>(); // (() -> ())
|
||||||
|
REQUIRE(groupTy);
|
||||||
|
CHECK(groupTy->type->is<AstTypeFunction>()); // () -> ()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
|
||||||
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
|
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -324,8 +324,7 @@ TEST_CASE_FIXTURE(Fixture, "free")
|
||||||
{
|
{
|
||||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||||
|
|
||||||
Type type{TypeVariant{FreeType{TypeLevel{0, 0}}}};
|
Type type{TypeVariant{FreeType{TypeLevel{0, 0}, builtinTypes->neverType, builtinTypes->unknownType}}};
|
||||||
|
|
||||||
ToDotOptions opts;
|
ToDotOptions opts;
|
||||||
opts.showPointers = false;
|
opts.showPointers = false;
|
||||||
CHECK_EQ(
|
CHECK_EQ(
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,6 +14,7 @@ using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||||
|
LUAU_FASTFLAG(LuauMetatableTypeFunctions)
|
||||||
|
|
||||||
struct TypeFunctionFixture : Fixture
|
struct TypeFunctionFixture : Fixture
|
||||||
{
|
{
|
||||||
|
@ -1262,4 +1263,195 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_len_type_function_follow")
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_assigns_correct_metatable")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Identity = setmetatable<{}, { __index: {} }>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
TypeId id = requireTypeAlias("Identity");
|
||||||
|
CHECK_EQ(toString(id, {true}), "{ @metatable { __index: { } }, { } }");
|
||||||
|
const MetatableType* mt = get<MetatableType>(id);
|
||||||
|
REQUIRE(mt);
|
||||||
|
CHECK_EQ(toString(mt->metatable), "{ __index: { } }");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_assigns_correct_metatable_2")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Identity = setmetatable<{}, { __index: {} }>
|
||||||
|
type FooBar = setmetatable<{}, Identity>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
TypeId id = requireTypeAlias("Identity");
|
||||||
|
CHECK_EQ(toString(id, {true}), "{ @metatable { __index: { } }, { } }");
|
||||||
|
const MetatableType* mt = get<MetatableType>(id);
|
||||||
|
REQUIRE(mt);
|
||||||
|
CHECK_EQ(toString(mt->metatable), "{ __index: { } }");
|
||||||
|
|
||||||
|
TypeId foobar = requireTypeAlias("FooBar");
|
||||||
|
const MetatableType* mt2 = get<MetatableType>(foobar);
|
||||||
|
REQUIRE(mt2);
|
||||||
|
CHECK_EQ(toString(mt2->metatable, {true}), "{ @metatable { __index: { } }, { } }");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_errors_on_metatable_with_metatable_metamethod")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Identity = setmetatable<{}, { __metatable: "blocked" }>
|
||||||
|
type Bad = setmetatable<Identity, { __index: {} }>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
TypeId id = requireTypeAlias("Identity");
|
||||||
|
CHECK_EQ(toString(id, {true}), "{ @metatable { __metatable: \"blocked\" }, { } }");
|
||||||
|
const MetatableType* mt = get<MetatableType>(id);
|
||||||
|
REQUIRE(mt);
|
||||||
|
CHECK_EQ(toString(mt->metatable), "{ __metatable: \"blocked\" }");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_errors_on_invalid_set")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Identity = setmetatable<string, {}>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_errors_on_nontable_metatable")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Identity = setmetatable<{}, string>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_type_function_returns_nil_if_no_metatable")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type TableWithNoMetatable = getmetatable<{}>
|
||||||
|
type NumberWithNoMetatable = getmetatable<number>
|
||||||
|
type BooleanWithNoMetatable = getmetatable<boolean>
|
||||||
|
type BooleanLiteralWithNoMetatable = getmetatable<true>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
auto tableResult = requireTypeAlias("TableWithNoMetatable");
|
||||||
|
CHECK_EQ(toString(tableResult), "nil");
|
||||||
|
|
||||||
|
auto numberResult = requireTypeAlias("NumberWithNoMetatable");
|
||||||
|
CHECK_EQ(toString(numberResult), "nil");
|
||||||
|
|
||||||
|
auto booleanResult = requireTypeAlias("BooleanWithNoMetatable");
|
||||||
|
CHECK_EQ(toString(booleanResult), "nil");
|
||||||
|
|
||||||
|
auto booleanLiteralResult = requireTypeAlias("BooleanLiteralWithNoMetatable");
|
||||||
|
CHECK_EQ(toString(booleanLiteralResult), "nil");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_correct_metatable")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local metatable = { __index = { w = 4 } }
|
||||||
|
local obj = setmetatable({x = 1, y = 2, z = 3}, metatable)
|
||||||
|
type Metatable = getmetatable<typeof(obj)>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ(toString(requireTypeAlias("Metatable"), {true}), "{ __index: { w: number } }");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_correct_metatable_for_union")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Identity = setmetatable<{}, {}>
|
||||||
|
type Metatable = getmetatable<string | Identity>
|
||||||
|
type IntersectMetatable = getmetatable<string & Identity>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
const PrimitiveType* stringType = get<PrimitiveType>(builtinTypes->stringType);
|
||||||
|
REQUIRE(stringType->metatable);
|
||||||
|
|
||||||
|
TypeArena arena = TypeArena{};
|
||||||
|
|
||||||
|
std::string expected1 = toString(arena.addType(UnionType{{*stringType->metatable, builtinTypes->emptyTableType}}), {true});
|
||||||
|
CHECK_EQ(toString(requireTypeAlias("Metatable"), {true}), expected1);
|
||||||
|
|
||||||
|
std::string expected2 = toString(arena.addType(IntersectionType{{*stringType->metatable, builtinTypes->emptyTableType}}), {true});
|
||||||
|
CHECK_EQ(toString(requireTypeAlias("IntersectMetatable"), {true}), expected2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_correct_metatable_for_string")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Metatable = getmetatable<string>
|
||||||
|
type Metatable2 = getmetatable<"foo">
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
const PrimitiveType* stringType = get<PrimitiveType>(builtinTypes->stringType);
|
||||||
|
REQUIRE(stringType->metatable);
|
||||||
|
|
||||||
|
std::string expected = toString(*stringType->metatable, {true});
|
||||||
|
|
||||||
|
CHECK_EQ(toString(requireTypeAlias("Metatable"), {true}), expected);
|
||||||
|
CHECK_EQ(toString(requireTypeAlias("Metatable2"), {true}), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_respects_metatable_metamethod")
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local metatable = { __metatable = "Test" }
|
||||||
|
local obj = setmetatable({x = 1, y = 2, z = 3}, metatable)
|
||||||
|
type Metatable = getmetatable<typeof(obj)>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ(toString(requireTypeAlias("Metatable")), "string");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauUserTypeFunFixNoReadWrite)
|
|
||||||
LUAU_FASTFLAG(LuauUserTypeFunFixInner)
|
LUAU_FASTFLAG(LuauUserTypeFunFixInner)
|
||||||
LUAU_FASTFLAG(LuauUserTypeFunGenerics)
|
LUAU_FASTFLAG(LuauUserTypeFunGenerics)
|
||||||
LUAU_FASTFLAG(LuauUserTypeFunCloneTail)
|
LUAU_FASTFLAG(LuauUserTypeFunCloneTail)
|
||||||
|
@ -667,7 +666,6 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works")
|
||||||
TEST_CASE_FIXTURE(ClassFixture, "write_of_readonly_is_nil")
|
TEST_CASE_FIXTURE(ClassFixture, "write_of_readonly_is_nil")
|
||||||
{
|
{
|
||||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||||
ScopedFastFlag udtfRwFix{FFlag::LuauUserTypeFunFixNoReadWrite, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type function getclass(arg)
|
type function getclass(arg)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "Fixture.h"
|
#include "Fixture.h"
|
||||||
|
|
||||||
|
#include "ScopedFlags.h"
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
#include "Luau/AstQuery.h"
|
#include "Luau/AstQuery.h"
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeAliases");
|
TEST_SUITE_BEGIN("TypeAliases");
|
||||||
|
|
||||||
|
@ -1178,4 +1180,33 @@ TEST_CASE_FIXTURE(Fixture, "bound_type_in_alias_segfault")
|
||||||
)"));
|
)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "gh1632_no_infinite_recursion_in_normalization")
|
||||||
|
{
|
||||||
|
ScopedFastFlag flags[] = {
|
||||||
|
{FFlag::LuauSolverV2, true},
|
||||||
|
{FFlag::LuauFixInfiniteRecursionInNormalization, true},
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Node<T> = {
|
||||||
|
value: T,
|
||||||
|
next: Node<T>?,
|
||||||
|
-- remove `prev`, solves issue
|
||||||
|
prev: Node<T>?,
|
||||||
|
};
|
||||||
|
|
||||||
|
type List<T> = {
|
||||||
|
head: Node<T>?
|
||||||
|
}
|
||||||
|
|
||||||
|
local function IsFront(list: List<any>, nodeB: Node<any>)
|
||||||
|
-- remove if statement below, solves issue
|
||||||
|
if (list.head == nodeB) then
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_CHECK_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -10,10 +10,9 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
LUAU_FASTFLAG(LuauTableCloneClonesType3)
|
||||||
LUAU_FASTFLAG(LuauStringFormatArityFix)
|
|
||||||
LUAU_FASTFLAG(LuauTableCloneClonesType2)
|
|
||||||
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
|
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
|
||||||
|
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("BuiltinTests");
|
TEST_SUITE_BEGIN("BuiltinTests");
|
||||||
|
|
||||||
|
@ -809,8 +808,6 @@ TEST_CASE_FIXTURE(Fixture, "string_format_as_method")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_trivial_arity")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_trivial_arity")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{FFlag::LuauStringFormatArityFix, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
string.format()
|
string.format()
|
||||||
)");
|
)");
|
||||||
|
@ -1134,15 +1131,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
if (FFlag::LuauSolverV2 && FFlag::LuauTypestateBuiltins2)
|
if (FFlag::LuauSolverV2)
|
||||||
CHECK("Key 'b' not found in table '{ read a: number }'" == toString(result.errors[0]));
|
CHECK("Key 'b' not found in table '{ read a: number }'" == toString(result.errors[0]));
|
||||||
else if (FFlag::LuauSolverV2)
|
|
||||||
CHECK("Key 'b' not found in table '{ a: number }'" == toString(result.errors[0]));
|
|
||||||
else
|
else
|
||||||
CHECK_EQ("Key 'b' not found in table '{| a: number |}'", toString(result.errors[0]));
|
CHECK_EQ("Key 'b' not found in table '{| a: number |}'", toString(result.errors[0]));
|
||||||
CHECK(Location({13, 18}, {13, 23}) == result.errors[0].location);
|
CHECK(Location({13, 18}, {13, 23}) == result.errors[0].location);
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2 && FFlag::LuauTypestateBuiltins2)
|
if (FFlag::LuauSolverV2)
|
||||||
{
|
{
|
||||||
CHECK_EQ("{ read a: number }", toString(requireTypeAtPosition({15, 19})));
|
CHECK_EQ("{ read a: number }", toString(requireTypeAtPosition({15, 19})));
|
||||||
CHECK_EQ("{ read b: string }", toString(requireTypeAtPosition({16, 19})));
|
CHECK_EQ("{ read b: string }", toString(requireTypeAtPosition({16, 19})));
|
||||||
|
@ -1178,7 +1173,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_does_not_retroactively_block_mu
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2 && FFlag::LuauTypestateBuiltins2)
|
if (FFlag::LuauSolverV2)
|
||||||
{
|
{
|
||||||
CHECK_EQ("{ a: number, q: string } | { read a: number, read q: string }", toString(requireType("t1"), {/*exhaustive */ true}));
|
CHECK_EQ("{ a: number, q: string } | { read a: number, read q: string }", toString(requireType("t1"), {/*exhaustive */ true}));
|
||||||
// before the assignment, it's `t1`
|
// before the assignment, it's `t1`
|
||||||
|
@ -1208,8 +1203,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_no_generic_table")
|
||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
if (FFlag::LuauTypestateBuiltins2)
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_on_metatable")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_on_metatable")
|
||||||
|
@ -1236,13 +1230,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_errors_on_no_args")
|
||||||
table.freeze()
|
table.freeze()
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// this does not error in the new solver without the typestate builtins functionality.
|
|
||||||
if (FFlag::LuauSolverV2 && !FFlag::LuauTypestateBuiltins2)
|
|
||||||
{
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
CHECK(get<CountMismatch>(result.errors[0]));
|
CHECK(get<CountMismatch>(result.errors[0]));
|
||||||
|
@ -1255,25 +1242,40 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_errors_on_non_tables")
|
||||||
table.freeze(42)
|
table.freeze(42)
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// this does not error in the new solver without the typestate builtins functionality.
|
|
||||||
if (FFlag::LuauSolverV2 && !FFlag::LuauTypestateBuiltins2)
|
|
||||||
{
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||||
REQUIRE(tm);
|
REQUIRE(tm);
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2 && FFlag::LuauTypestateBuiltins2)
|
if (FFlag::LuauSolverV2)
|
||||||
CHECK_EQ(toString(tm->wantedType), "table");
|
CHECK_EQ(toString(tm->wantedType), "table");
|
||||||
else
|
else
|
||||||
CHECK_EQ(toString(tm->wantedType), "{- -}");
|
CHECK_EQ(toString(tm->wantedType), "{- -}");
|
||||||
CHECK_EQ(toString(tm->givenType), "number");
|
CHECK_EQ(toString(tm->givenType), "number");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_persistent_skip")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauFreezeIgnorePersistent{FFlag::LuauFreezeIgnorePersistent, true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
table.freeze(table)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_clone_persistent_skip")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauFreezeIgnorePersistent{FFlag::LuauFreezeIgnorePersistent, true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
table.clone(table)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
|
||||||
{
|
{
|
||||||
// In the new solver, nil can certainly be used where a generic is required, so all generic parameters are optional.
|
// In the new solver, nil can certainly be used where a generic is required, so all generic parameters are optional.
|
||||||
|
@ -1599,7 +1601,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_dot_clone_type_states")
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::LuauTableCloneClonesType2)
|
if (FFlag::LuauTableCloneClonesType3)
|
||||||
{
|
{
|
||||||
CHECK_EQ(toString(requireType("t1"), {true}), "{ x: number, z: number }");
|
CHECK_EQ(toString(requireType("t1"), {true}), "{ x: number, z: number }");
|
||||||
CHECK_EQ(toString(requireType("t2"), {true}), "{ x: number, y: number }");
|
CHECK_EQ(toString(requireType("t2"), {true}), "{ x: number, y: number }");
|
||||||
|
@ -1611,6 +1613,40 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_dot_clone_type_states")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_clone_should_not_break")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local Immutable = {}
|
||||||
|
|
||||||
|
function Immutable.Set(dictionary, key, value)
|
||||||
|
local new = table.clone(dictionary)
|
||||||
|
|
||||||
|
new[key] = value
|
||||||
|
|
||||||
|
return new
|
||||||
|
end
|
||||||
|
|
||||||
|
return Immutable
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_clone_should_not_break_2")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
function set(dictionary, key, value)
|
||||||
|
local new = table.clone(dictionary)
|
||||||
|
|
||||||
|
new[key] = value
|
||||||
|
|
||||||
|
return new
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any")
|
||||||
{
|
{
|
||||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauNewSolverPrePopulateClasses)
|
LUAU_FASTFLAG(LuauNewSolverPrePopulateClasses)
|
||||||
|
LUAU_FASTFLAG(LuauClipNestedAndRecursiveUnion)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("DefinitionTests");
|
TEST_SUITE_BEGIN("DefinitionTests");
|
||||||
|
|
||||||
|
@ -541,4 +542,20 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")
|
||||||
CHECK_EQ(ctv->definitionModuleName, "@test");
|
CHECK_EQ(ctv->definitionModuleName, "@test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully")
|
||||||
|
{
|
||||||
|
ScopedFastFlag _{FFlag::LuauClipNestedAndRecursiveUnion, true};
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||||
|
local t: {[string]: string} = {}
|
||||||
|
|
||||||
|
local function f()
|
||||||
|
t = t
|
||||||
|
end
|
||||||
|
|
||||||
|
t = t
|
||||||
|
)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -19,7 +19,6 @@ using namespace Luau;
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||||
LUAU_FASTFLAG(LuauRetrySubtypingWithoutHiddenPack)
|
|
||||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||||
|
@ -3012,9 +3011,6 @@ local u,v = id(3), id(id(44))
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "hidden_variadics_should_not_break_subtyping")
|
TEST_CASE_FIXTURE(Fixture, "hidden_variadics_should_not_break_subtyping")
|
||||||
{
|
{
|
||||||
// Only applies to new solver.
|
|
||||||
ScopedFastFlag sff{FFlag::LuauRetrySubtypingWithoutHiddenPack, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!strict
|
--!strict
|
||||||
type FooType = {
|
type FooType = {
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
|
||||||
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
|
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
@ -185,10 +184,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_table_freeze")
|
||||||
ModulePtr b = frontend.moduleResolver.getModule("game/B");
|
ModulePtr b = frontend.moduleResolver.getModule("game/B");
|
||||||
REQUIRE(b != nullptr);
|
REQUIRE(b != nullptr);
|
||||||
// confirm that no cross-module mutation happened here!
|
// confirm that no cross-module mutation happened here!
|
||||||
if (FFlag::LuauSolverV2 && FFlag::LuauTypestateBuiltins2)
|
if (FFlag::LuauSolverV2)
|
||||||
CHECK(toString(b->returnType) == "{ read a: number }");
|
CHECK(toString(b->returnType) == "{ read a: number }");
|
||||||
else if (FFlag::LuauSolverV2)
|
|
||||||
CHECK(toString(b->returnType) == "{ a: number }");
|
|
||||||
else
|
else
|
||||||
CHECK(toString(b->returnType) == "{| a: number |}");
|
CHECK(toString(b->returnType) == "{| a: number |}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauDoNotGeneralizeInTypeFunctions)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferOperators");
|
TEST_SUITE_BEGIN("TypeInferOperators");
|
||||||
|
|
||||||
|
@ -814,19 +815,19 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "and_binexps_dont_unify")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "and_binexps_dont_unify")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
ScopedFastFlag _{FFlag::LuauDoNotGeneralizeInTypeFunctions, true};
|
||||||
--!strict
|
|
||||||
local t = {}
|
|
||||||
while true and t[1] do
|
|
||||||
print(t[1].test)
|
|
||||||
end
|
|
||||||
)");
|
|
||||||
|
|
||||||
// This infers a type for `t` of `{unknown}`, and so it makes sense that `t[1].test` would error.
|
// `t` will be inferred to be of type `{ { test: unknown } }` which is
|
||||||
if (FFlag::LuauSolverV2)
|
// reasonable, in that it's empty with no bounds on its members. Optimally
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
// we might emit an error here that the `print(...)` expression is
|
||||||
else
|
// unreachable.
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||||
|
--!strict
|
||||||
|
local t = {}
|
||||||
|
while true and t[1] do
|
||||||
|
print(t[1].test)
|
||||||
|
end
|
||||||
|
)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operators")
|
TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operators")
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
#include "Luau/RecursionCounter.h"
|
#include "Luau/RecursionCounter.h"
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2);
|
||||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification);
|
LUAU_FASTFLAG(DebugLuauEqSatSimplification);
|
||||||
|
LUAU_FASTFLAG(LuauStoreCSTData);
|
||||||
LUAU_FASTINT(LuauNormalizeCacheLimit);
|
LUAU_FASTINT(LuauNormalizeCacheLimit);
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit);
|
LUAU_FASTINT(LuauTarjanChildLimit);
|
||||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||||
|
@ -46,7 +48,16 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
||||||
end
|
end
|
||||||
)";
|
)";
|
||||||
|
|
||||||
const std::string expected = R"(
|
const std::string expected = FFlag::LuauStoreCSTData ? R"(
|
||||||
|
function f(a:{fn:()->(a,b...)}): ()
|
||||||
|
if type(a) == 'boolean' then
|
||||||
|
local a1:boolean=a
|
||||||
|
elseif a.fn() then
|
||||||
|
local a2:{fn:()->(a,b...)}=a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)"
|
||||||
|
: R"(
|
||||||
function f(a:{fn:()->(a,b...)}): ()
|
function f(a:{fn:()->(a,b...)}): ()
|
||||||
if type(a) == 'boolean'then
|
if type(a) == 'boolean'then
|
||||||
local a1:boolean=a
|
local a1:boolean=a
|
||||||
|
@ -56,7 +67,16 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
||||||
end
|
end
|
||||||
)";
|
)";
|
||||||
|
|
||||||
const std::string expectedWithNewSolver = R"(
|
const std::string expectedWithNewSolver = FFlag::LuauStoreCSTData ? R"(
|
||||||
|
function f(a:{fn:()->(unknown,...unknown)}): ()
|
||||||
|
if type(a) == 'boolean' then
|
||||||
|
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
||||||
|
elseif a.fn() then
|
||||||
|
local a2:{fn:()->(unknown,...unknown)}&(class|function|nil|number|string|thread|buffer|table)=a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)"
|
||||||
|
: R"(
|
||||||
function f(a:{fn:()->(unknown,...unknown)}): ()
|
function f(a:{fn:()->(unknown,...unknown)}): ()
|
||||||
if type(a) == 'boolean'then
|
if type(a) == 'boolean'then
|
||||||
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
||||||
|
@ -66,7 +86,16 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
||||||
end
|
end
|
||||||
)";
|
)";
|
||||||
|
|
||||||
const std::string expectedWithEqSat = R"(
|
const std::string expectedWithEqSat = FFlag::LuauStoreCSTData ? R"(
|
||||||
|
function f(a:{fn:()->(unknown,...unknown)}): ()
|
||||||
|
if type(a) == 'boolean' then
|
||||||
|
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
||||||
|
elseif a.fn() then
|
||||||
|
local a2:{fn:()->(unknown,...unknown)}&negate<boolean>=a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)"
|
||||||
|
: R"(
|
||||||
function f(a:{fn:()->(unknown,...unknown)}): ()
|
function f(a:{fn:()->(unknown,...unknown)}): ()
|
||||||
if type(a) == 'boolean'then
|
if type(a) == 'boolean'then
|
||||||
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
||||||
|
@ -535,10 +564,10 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
||||||
|
|
||||||
std::unique_ptr scope = std::make_unique<Scope>(builtinTypes->anyTypePack);
|
std::unique_ptr scope = std::make_unique<Scope>(builtinTypes->anyTypePack);
|
||||||
|
|
||||||
TypeId free1 = arena.addType(FreeType{scope.get()});
|
TypeId free1 = arena.freshType(builtinTypes, scope.get());
|
||||||
TypeId option1 = arena.addType(UnionType{{nilType, free1}});
|
TypeId option1 = arena.addType(UnionType{{nilType, free1}});
|
||||||
|
|
||||||
TypeId free2 = arena.addType(FreeType{scope.get()});
|
TypeId free2 = arena.freshType(builtinTypes, scope.get());
|
||||||
TypeId option2 = arena.addType(UnionType{{nilType, free2}});
|
TypeId option2 = arena.addType(UnionType{{nilType, free2}});
|
||||||
|
|
||||||
InternalErrorReporter iceHandler;
|
InternalErrorReporter iceHandler;
|
||||||
|
@ -965,10 +994,10 @@ TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
|
||||||
|
|
||||||
std::unique_ptr scope = std::make_unique<Scope>(builtinTypes->anyTypePack);
|
std::unique_ptr scope = std::make_unique<Scope>(builtinTypes->anyTypePack);
|
||||||
|
|
||||||
TypeId free1 = arena.addType(FreeType{scope.get()});
|
TypeId free1 = arena.freshType(builtinTypes, scope.get());
|
||||||
TypeId option1 = arena.addType(UnionType{{nilType, free1}});
|
TypeId option1 = arena.addType(UnionType{{nilType, free1}});
|
||||||
|
|
||||||
TypeId free2 = arena.addType(FreeType{scope.get()});
|
TypeId free2 = arena.freshType(builtinTypes, scope.get());
|
||||||
TypeId option2 = arena.addType(UnionType{{nilType, free2}});
|
TypeId option2 = arena.addType(UnionType{{nilType, free2}});
|
||||||
|
|
||||||
InternalErrorReporter iceHandler;
|
InternalErrorReporter iceHandler;
|
||||||
|
@ -1284,7 +1313,7 @@ TEST_CASE_FIXTURE(Fixture, "table_containing_non_final_type_is_erroneously_cache
|
||||||
TableType* table = getMutable<TableType>(tableTy);
|
TableType* table = getMutable<TableType>(tableTy);
|
||||||
REQUIRE(table);
|
REQUIRE(table);
|
||||||
|
|
||||||
TypeId freeTy = arena.freshType(&globalScope);
|
TypeId freeTy = arena.freshType(builtinTypes, &globalScope);
|
||||||
|
|
||||||
table->props["foo"] = Property::rw(freeTy);
|
table->props["foo"] = Property::rw(freeTy);
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ using namespace Luau;
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
||||||
LUAU_FASTFLAG(LuauRetrySubtypingWithoutHiddenPack)
|
|
||||||
LUAU_FASTFLAG(LuauTableKeysAreRValues)
|
LUAU_FASTFLAG(LuauTableKeysAreRValues)
|
||||||
LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer)
|
LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer)
|
||||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||||
|
@ -2378,7 +2377,7 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
||||||
local c : string = t.m("hi")
|
local c : string = t.m("hi")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
if (FFlag::LuauSolverV2 && FFlag::LuauRetrySubtypingWithoutHiddenPack)
|
if (FFlag::LuauSolverV2)
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
@ -2387,15 +2386,6 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
||||||
// This is not actually the expected behavior, but the typemismatch we were seeing before was for the wrong reason.
|
// This is not actually the expected behavior, but the typemismatch we were seeing before was for the wrong reason.
|
||||||
// The behavior of this test is just regressed generally in the new solver, and will need to be consciously addressed.
|
// The behavior of this test is just regressed generally in the new solver, and will need to be consciously addressed.
|
||||||
}
|
}
|
||||||
else if (FFlag::LuauSolverV2)
|
|
||||||
{
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
||||||
|
|
||||||
CHECK(get<TypeMismatch>(result.errors[0]));
|
|
||||||
CHECK(Location{{6, 45}, {6, 46}} == result.errors[0].location);
|
|
||||||
|
|
||||||
CHECK(get<ExplicitFunctionAnnotationRecommended>(result.errors[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: test behavior is wrong with LuauInstantiateInSubtyping until we can re-enable the covariant requirement for instantiation in subtyping
|
// TODO: test behavior is wrong with LuauInstantiateInSubtyping until we can re-enable the covariant requirement for instantiation in subtyping
|
||||||
else if (FFlag::LuauInstantiateInSubtyping)
|
else if (FFlag::LuauInstantiateInSubtyping)
|
||||||
|
|
|
@ -24,6 +24,7 @@ LUAU_FASTINT(LuauNormalizeCacheLimit);
|
||||||
LUAU_FASTINT(LuauRecursionLimit);
|
LUAU_FASTINT(LuauRecursionLimit);
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
||||||
LUAU_FASTFLAG(InferGlobalTypes)
|
LUAU_FASTFLAG(InferGlobalTypes)
|
||||||
|
LUAU_FASTFLAG(LuauAstTypeGroup)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
@ -1201,7 +1202,10 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
|
||||||
CHECK(3 == result.errors.size());
|
CHECK(3 == result.errors.size());
|
||||||
CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].location);
|
CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].location);
|
||||||
CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location);
|
CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location);
|
||||||
CHECK(Location{{3, 23}, {3, 40}} == result.errors[2].location);
|
if (FFlag::LuauAstTypeGroup)
|
||||||
|
CHECK(Location{{3, 22}, {3, 40}} == result.errors[2].location);
|
||||||
|
else
|
||||||
|
CHECK(Location{{3, 23}, {3, 40}} == result.errors[2].location);
|
||||||
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
|
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
|
||||||
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[1]));
|
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[1]));
|
||||||
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[2]));
|
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[2]));
|
||||||
|
|
|
@ -42,12 +42,13 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "primitives_unify")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified")
|
TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified")
|
||||||
{
|
{
|
||||||
Type functionOne{TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))
|
Type functionOne{TypeVariant{
|
||||||
|
FunctionType(arena.addTypePack({arena.freshType(builtinTypes, globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))
|
||||||
}};
|
}};
|
||||||
|
|
||||||
Type functionTwo{
|
Type functionTwo{TypeVariant{FunctionType(
|
||||||
TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({arena.freshType(globalScope->level)}))}
|
arena.addTypePack({arena.freshType(builtinTypes, globalScope->level)}), arena.addTypePack({arena.freshType(builtinTypes, globalScope->level)})
|
||||||
};
|
)}};
|
||||||
|
|
||||||
state.tryUnify(&functionTwo, &functionOne);
|
state.tryUnify(&functionTwo, &functionOne);
|
||||||
CHECK(!state.failure);
|
CHECK(!state.failure);
|
||||||
|
@ -60,14 +61,16 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_functions_are_preserved")
|
TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_functions_are_preserved")
|
||||||
{
|
{
|
||||||
TypePackVar argPackOne{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}};
|
TypePackVar argPackOne{TypePack{{arena.freshType(builtinTypes, globalScope->level)}, std::nullopt}};
|
||||||
Type functionOne{TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))
|
Type functionOne{TypeVariant{
|
||||||
|
FunctionType(arena.addTypePack({arena.freshType(builtinTypes, globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))
|
||||||
}};
|
}};
|
||||||
|
|
||||||
Type functionOneSaved = functionOne.clone();
|
Type functionOneSaved = functionOne.clone();
|
||||||
|
|
||||||
TypePackVar argPackTwo{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}};
|
TypePackVar argPackTwo{TypePack{{arena.freshType(builtinTypes, globalScope->level)}, std::nullopt}};
|
||||||
Type functionTwo{TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->stringType}))
|
Type functionTwo{TypeVariant{
|
||||||
|
FunctionType(arena.addTypePack({arena.freshType(builtinTypes, globalScope->level)}), arena.addTypePack({builtinTypes->stringType}))
|
||||||
}};
|
}};
|
||||||
|
|
||||||
Type functionTwoSaved = functionTwo.clone();
|
Type functionTwoSaved = functionTwo.clone();
|
||||||
|
@ -83,11 +86,11 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_functions_are_preserved")
|
||||||
TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified")
|
TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified")
|
||||||
{
|
{
|
||||||
Type tableOne{TypeVariant{
|
Type tableOne{TypeVariant{
|
||||||
TableType{{{"foo", {arena.freshType(globalScope->level)}}}, std::nullopt, globalScope->level, TableState::Unsealed},
|
TableType{{{"foo", {arena.freshType(builtinTypes, globalScope->level)}}}, std::nullopt, globalScope->level, TableState::Unsealed},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
Type tableTwo{TypeVariant{
|
Type tableTwo{TypeVariant{
|
||||||
TableType{{{"foo", {arena.freshType(globalScope->level)}}}, std::nullopt, globalScope->level, TableState::Unsealed},
|
TableType{{{"foo", {arena.freshType(builtinTypes, globalScope->level)}}}, std::nullopt, globalScope->level, TableState::Unsealed},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
CHECK_NE(*getMutable<TableType>(&tableOne)->props["foo"].type(), *getMutable<TableType>(&tableTwo)->props["foo"].type());
|
CHECK_NE(*getMutable<TableType>(&tableOne)->props["foo"].type(), *getMutable<TableType>(&tableTwo)->props["foo"].type());
|
||||||
|
@ -106,7 +109,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
|
||||||
{
|
{
|
||||||
Type tableOne{TypeVariant{
|
Type tableOne{TypeVariant{
|
||||||
TableType{
|
TableType{
|
||||||
{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->numberType}}},
|
{{"foo", {arena.freshType(builtinTypes, globalScope->level)}}, {"bar", {builtinTypes->numberType}}},
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
globalScope->level,
|
globalScope->level,
|
||||||
TableState::Unsealed
|
TableState::Unsealed
|
||||||
|
@ -115,7 +118,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
|
||||||
|
|
||||||
Type tableTwo{TypeVariant{
|
Type tableTwo{TypeVariant{
|
||||||
TableType{
|
TableType{
|
||||||
{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->stringType}}},
|
{{"foo", {arena.freshType(builtinTypes, globalScope->level)}}, {"bar", {builtinTypes->stringType}}},
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
globalScope->level,
|
globalScope->level,
|
||||||
TableState::Unsealed
|
TableState::Unsealed
|
||||||
|
@ -295,7 +298,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag")
|
TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag")
|
||||||
{
|
{
|
||||||
Type redirect{FreeType{TypeLevel{}}};
|
Type redirect{FreeType{TypeLevel{}, builtinTypes->neverType, builtinTypes->unknownType}};
|
||||||
Type table{TableType{}};
|
Type table{TableType{}};
|
||||||
Type metatable{MetatableType{&redirect, &table}};
|
Type metatable{MetatableType{&redirect, &table}};
|
||||||
redirect = BoundType{&metatable}; // Now we have a metatable that is recursive on the table type
|
redirect = BoundType{&metatable}; // Now we have a metatable that is recursive on the table type
|
||||||
|
@ -318,7 +321,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "cli_50320_follow_in_any_unification")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_type_owner")
|
TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_type_owner")
|
||||||
{
|
{
|
||||||
TypeId a = arena.addType(Type{FreeType{TypeLevel{}}});
|
TypeId a = arena.freshType(builtinTypes, TypeLevel{});
|
||||||
TypeId b = builtinTypes->numberType;
|
TypeId b = builtinTypes->numberType;
|
||||||
|
|
||||||
state.tryUnify(a, b);
|
state.tryUnify(a, b);
|
||||||
|
@ -381,7 +384,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
|
||||||
TypePackVar packTmp{TypePack{{builtinTypes->anyType}, &variadicAny}};
|
TypePackVar packTmp{TypePack{{builtinTypes->anyType}, &variadicAny}};
|
||||||
TypePackVar packSub{TypePack{{builtinTypes->anyType, builtinTypes->anyType}, &packTmp}};
|
TypePackVar packSub{TypePack{{builtinTypes->anyType, builtinTypes->anyType}, &packTmp}};
|
||||||
|
|
||||||
Type freeTy{FreeType{TypeLevel{}}};
|
Type freeTy{FreeType{TypeLevel{}, builtinTypes->neverType, builtinTypes->unknownType}};
|
||||||
TypePackVar freeTp{FreeTypePack{TypeLevel{}}};
|
TypePackVar freeTp{FreeTypePack{TypeLevel{}}};
|
||||||
TypePackVar packSuper{TypePack{{&freeTy}, &freeTp}};
|
TypePackVar packSuper{TypePack{{&freeTy}, &freeTp}};
|
||||||
|
|
||||||
|
@ -438,10 +441,10 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "unifying_two_unions_under_dcr_does_not_creat
|
||||||
const std::shared_ptr<Scope> scope = globalScope;
|
const std::shared_ptr<Scope> scope = globalScope;
|
||||||
const std::shared_ptr<Scope> nestedScope = std::make_shared<Scope>(scope);
|
const std::shared_ptr<Scope> nestedScope = std::make_shared<Scope>(scope);
|
||||||
|
|
||||||
const TypeId outerType = arena.freshType(scope.get());
|
const TypeId outerType = arena.freshType(builtinTypes, scope.get());
|
||||||
const TypeId outerType2 = arena.freshType(scope.get());
|
const TypeId outerType2 = arena.freshType(builtinTypes, scope.get());
|
||||||
|
|
||||||
const TypeId innerType = arena.freshType(nestedScope.get());
|
const TypeId innerType = arena.freshType(builtinTypes, nestedScope.get());
|
||||||
|
|
||||||
state.enableNewSolver();
|
state.enableNewSolver();
|
||||||
|
|
||||||
|
|
|
@ -621,7 +621,7 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
|
||||||
TypeArena& arena = frontend.globals.globalTypes;
|
TypeArena& arena = frontend.globals.globalTypes;
|
||||||
unfreeze(arena);
|
unfreeze(arena);
|
||||||
|
|
||||||
TypeId badCyclicUnionTy = arena.freshType(frontend.globals.globalScope.get());
|
TypeId badCyclicUnionTy = arena.freshType(builtinTypes, frontend.globals.globalScope.get());
|
||||||
UnionType u;
|
UnionType u;
|
||||||
|
|
||||||
u.options.push_back(badCyclicUnionTy);
|
u.options.push_back(badCyclicUnionTy);
|
||||||
|
|
|
@ -17,6 +17,7 @@ using namespace Luau::TypePath;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2);
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
|
LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
|
||||||
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds);
|
||||||
|
|
||||||
struct TypePathFixture : Fixture
|
struct TypePathFixture : Fixture
|
||||||
{
|
{
|
||||||
|
@ -277,7 +278,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "bounds")
|
||||||
TypeArena& arena = frontend.globals.globalTypes;
|
TypeArena& arena = frontend.globals.globalTypes;
|
||||||
unfreeze(arena);
|
unfreeze(arena);
|
||||||
|
|
||||||
TypeId ty = arena.freshType(frontend.globals.globalScope.get());
|
TypeId ty = arena.freshType(frontend.builtinTypes, frontend.globals.globalScope.get());
|
||||||
FreeType* ft = getMutable<FreeType>(ty);
|
FreeType* ft = getMutable<FreeType>(ty);
|
||||||
|
|
||||||
SUBCASE("upper")
|
SUBCASE("upper")
|
||||||
|
|
|
@ -219,7 +219,7 @@ TEST_CASE_FIXTURE(Fixture, "UnionTypeIterator_with_only_cyclic_union")
|
||||||
*/
|
*/
|
||||||
TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
|
TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
|
||||||
{
|
{
|
||||||
Type ftv11{FreeType{TypeLevel{}}};
|
Type ftv11{FreeType{TypeLevel{}, builtinTypes->neverType, builtinTypes->unknownType}};
|
||||||
|
|
||||||
TypePackVar tp24{TypePack{{&ftv11}}};
|
TypePackVar tp24{TypePack{{&ftv11}}};
|
||||||
TypePackVar tp17{TypePack{}};
|
TypePackVar tp17{TypePack{}};
|
||||||
|
@ -469,8 +469,8 @@ TEST_CASE("content_reassignment")
|
||||||
myAny.documentationSymbol = "@global/any";
|
myAny.documentationSymbol = "@global/any";
|
||||||
|
|
||||||
TypeArena arena;
|
TypeArena arena;
|
||||||
|
BuiltinTypes builtinTypes;
|
||||||
TypeId futureAny = arena.addType(FreeType{TypeLevel{}});
|
TypeId futureAny = arena.freshType(NotNull{&builtinTypes}, TypeLevel{});
|
||||||
asMutable(futureAny)->reassign(myAny);
|
asMutable(futureAny)->reassign(myAny);
|
||||||
|
|
||||||
CHECK(get<AnyType>(futureAny) != nullptr);
|
CHECK(get<AnyType>(futureAny) != nullptr);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "Luau/RecursionCounter.h"
|
#include "Luau/RecursionCounter.h"
|
||||||
|
|
||||||
|
#include "Luau/Type.h"
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
@ -54,7 +55,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_throw_when_limit_is_high_enough")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "some_free_types_do_not_have_bounds")
|
TEST_CASE_FIXTURE(Fixture, "some_free_types_do_not_have_bounds")
|
||||||
{
|
{
|
||||||
Type t{FreeType{TypeLevel{}}};
|
Type t{FreeType{TypeLevel{}, builtinTypes->neverType, builtinTypes->unknownType}};
|
||||||
|
|
||||||
(void)toString(&t);
|
(void)toString(&t);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue