mirror of
https://github.com/luau-lang/luau.git
synced 2025-03-03 18:51:42 +00:00
Sync to upstream/release/660
This commit is contained in:
parent
5f0bd2fcdd
commit
7199da820f
73 changed files with 5862 additions and 519 deletions
|
@ -4,6 +4,7 @@
|
|||
#include <Luau/NotNull.h>
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Scope.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
|
@ -26,13 +27,17 @@ struct CloneState
|
|||
* while `clone` will make a deep copy of the entire type and its every component.
|
||||
*
|
||||
* 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);
|
||||
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState);
|
||||
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
|
||||
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent = false);
|
||||
|
||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
|
||||
TypeId clone(TypeId tp, TypeArena& dest, CloneState& cloneState);
|
||||
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState);
|
||||
Binding clone(const Binding& binding, TypeArena& dest, CloneState& cloneState);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/RequireTracer.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Set.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
|
@ -56,13 +57,32 @@ struct SourceNode
|
|||
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;
|
||||
std::string humanReadableName;
|
||||
DenseHashSet<ModuleName> requireSet{{}};
|
||||
std::vector<std::pair<ModuleName, Location>> requireLocations;
|
||||
Set<ModuleName> dependents{{}};
|
||||
|
||||
bool dirtySourceModule = true;
|
||||
bool dirtyModule = true;
|
||||
bool dirtyModuleForAutocomplete = true;
|
||||
|
||||
bool invalidModuleDependency = true;
|
||||
bool invalidModuleDependencyForAutocomplete = true;
|
||||
|
||||
double autocompleteLimitsMult = 1.0;
|
||||
};
|
||||
|
||||
|
@ -117,7 +137,7 @@ struct FrontendModuleResolver : ModuleResolver
|
|||
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) 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();
|
||||
|
||||
private:
|
||||
|
@ -151,9 +171,13 @@ struct Frontend
|
|||
// Parse and typecheck module graph
|
||||
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;
|
||||
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.
|
||||
*
|
||||
* Returns nullptr if we don't have it. This could mean that the script
|
||||
|
|
|
@ -19,10 +19,10 @@ struct SimplifyResult
|
|||
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 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
|
||||
{
|
||||
|
|
|
@ -69,12 +69,16 @@ using Name = std::string;
|
|||
// A free type is one whose exact shape has yet to be fully determined.
|
||||
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(Scope* scope);
|
||||
FreeType(Scope* scope, TypeLevel level);
|
||||
|
||||
FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound);
|
||||
|
||||
int index;
|
||||
TypeLevel level;
|
||||
Scope* scope = nullptr;
|
||||
|
|
|
@ -32,9 +32,13 @@ struct TypeArena
|
|||
|
||||
TypeId addTV(Type&& tv);
|
||||
|
||||
TypeId freshType(TypeLevel level);
|
||||
TypeId freshType(Scope* scope);
|
||||
TypeId freshType(Scope* scope, TypeLevel level);
|
||||
TypeId freshType(NotNull<BuiltinTypes> builtins, TypeLevel level);
|
||||
TypeId freshType(NotNull<BuiltinTypes> builtins, Scope* scope);
|
||||
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);
|
||||
|
||||
|
|
|
@ -241,6 +241,9 @@ struct BuiltinTypeFunctions
|
|||
TypeFunction indexFunc;
|
||||
TypeFunction rawgetFunc;
|
||||
|
||||
TypeFunction setmetatableFunc;
|
||||
TypeFunction getmetatableFunc;
|
||||
|
||||
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
|
||||
{
|
||||
writeNode(
|
||||
|
|
|
@ -29,12 +29,11 @@
|
|||
*/
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
|
||||
LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -449,10 +448,9 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
ttv->props["foreachi"].deprecated = true;
|
||||
|
||||
attachMagicFunction(ttv->props["pack"].type(), std::make_shared<MagicPack>());
|
||||
if (FFlag::LuauTableCloneClonesType2)
|
||||
if (FFlag::LuauTableCloneClonesType3)
|
||||
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)
|
||||
|
@ -613,10 +611,7 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1401,7 +1396,7 @@ std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
|
|||
WithPredicate<TypePackId> withPredicate
|
||||
)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTableCloneClonesType2);
|
||||
LUAU_ASSERT(FFlag::LuauTableCloneClonesType3);
|
||||
|
||||
auto [paramPack, _predicates] = withPredicate;
|
||||
|
||||
|
@ -1416,6 +1411,9 @@ std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
|
|||
|
||||
TypeId inputType = follow(paramTypes[0]);
|
||||
|
||||
if (!get<TableType>(inputType))
|
||||
return std::nullopt;
|
||||
|
||||
CloneState cloneState{typechecker.builtinTypes};
|
||||
TypeId resultType = shallowClone(inputType, arena, cloneState);
|
||||
|
||||
|
@ -1425,7 +1423,7 @@ std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
|
|||
|
||||
bool MagicClone::infer(const MagicFunctionCallContext& context)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTableCloneClonesType2);
|
||||
LUAU_ASSERT(FFlag::LuauTableCloneClonesType3);
|
||||
|
||||
TypeArena* arena = context.solver->arena;
|
||||
|
||||
|
@ -1438,8 +1436,11 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
|
|||
|
||||
TypeId inputType = follow(paramTypes[0]);
|
||||
|
||||
if (!get<TableType>(inputType))
|
||||
return false;
|
||||
|
||||
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))
|
||||
{
|
||||
|
@ -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.
|
||||
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);
|
||||
// `clone` should not break this.
|
||||
LUAU_ASSERT(tableTy);
|
||||
|
@ -1507,8 +1508,6 @@ std::optional<WithPredicate<TypePackId>> MagicFreeze::handleOldSolver(struct Typ
|
|||
|
||||
bool MagicFreeze::infer(const MagicFunctionCallContext& context)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTypestateBuiltins2);
|
||||
|
||||
TypeArena* arena = context.solver->arena;
|
||||
const DataFlowGraph* dfg = context.solver->dfg.get();
|
||||
Scope* scope = context.constraint->scope.get();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "Luau/Unifiable.h"
|
||||
|
||||
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.
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
||||
|
@ -38,14 +39,26 @@ class TypeCloner
|
|||
NotNull<SeenTypes> types;
|
||||
NotNull<SeenTypePacks> packs;
|
||||
|
||||
TypeId forceTy = nullptr;
|
||||
TypePackId forceTp = nullptr;
|
||||
|
||||
int steps = 0;
|
||||
|
||||
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)
|
||||
, builtinTypes(builtinTypes)
|
||||
, types(types)
|
||||
, packs(packs)
|
||||
, forceTy(forceTy)
|
||||
, forceTp(forceTp)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -112,7 +125,7 @@ private:
|
|||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||
if (auto it = types->find(ty); it != types->end())
|
||||
return it->second;
|
||||
else if (ty->persistent)
|
||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||
return ty;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -122,7 +135,7 @@ private:
|
|||
tp = follow(tp);
|
||||
if (auto it = packs->find(tp); it != packs->end())
|
||||
return it->second;
|
||||
else if (tp->persistent)
|
||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||
return tp;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -148,7 +161,7 @@ public:
|
|||
|
||||
if (auto clone = find(ty))
|
||||
return *clone;
|
||||
else if (ty->persistent)
|
||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||
return ty;
|
||||
|
||||
TypeId target = arena->addType(ty->ty);
|
||||
|
@ -174,7 +187,7 @@ public:
|
|||
|
||||
if (auto clone = find(tp))
|
||||
return *clone;
|
||||
else if (tp->persistent)
|
||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||
return tp;
|
||||
|
||||
TypePackId target = arena->addTypePack(tp->ty);
|
||||
|
@ -458,21 +471,37 @@ private:
|
|||
|
||||
} // 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -481,7 +510,7 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
|||
if (tp->persistent)
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -490,13 +519,13 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
|
|||
if (typeId->persistent)
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
@ -521,4 +550,18 @@ TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
|
|||
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
|
||||
|
|
|
@ -31,13 +31,14 @@
|
|||
LUAU_FASTINT(LuauCheckRecursionLimit)
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(InferGlobalTypes)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
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});
|
||||
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});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (const AstExprGlobal* global = call->func->as<AstExprGlobal>(); global && global->name == "setmetatable")
|
||||
{
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2068,7 +2059,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
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];
|
||||
auto resultTy = arena->addType(BlockedType{});
|
||||
|
@ -2217,7 +2208,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
|
|||
if (forceSingleton)
|
||||
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.upperBound = builtinTypes->stringType;
|
||||
const TypeId freeTy = arena->addType(ft);
|
||||
|
@ -2231,7 +2223,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
|
|||
if (forceSingleton)
|
||||
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.upperBound = builtinTypes->booleanType;
|
||||
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>())
|
||||
{
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
{
|
||||
if (unionAnnotation->types.size == 1)
|
||||
return resolveType(scope, unionAnnotation->types.data[0], inTypeArguments);
|
||||
}
|
||||
|
||||
std::vector<TypeId> parts;
|
||||
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>())
|
||||
{
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
{
|
||||
if (intersectionAnnotation->types.size == 1)
|
||||
return resolveType(scope, intersectionAnnotation->types.data[0], inTypeArguments);
|
||||
}
|
||||
|
||||
std::vector<TypeId> parts;
|
||||
for (AstType* part : intersectionAnnotation->types)
|
||||
{
|
||||
|
@ -3445,6 +3450,10 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
|||
|
||||
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>())
|
||||
{
|
||||
if (boolAnnotation->value)
|
||||
|
|
|
@ -31,7 +31,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
|
||||
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRemoveNotAnyHack)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
|
||||
|
@ -1161,22 +1160,8 @@ void ConstraintSolver::fillInDiscriminantTypes(
|
|||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1313,22 +1298,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -879,7 +878,7 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprCall* c)
|
|||
{
|
||||
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();
|
||||
|
||||
|
@ -1170,6 +1169,8 @@ void DataFlowGraphBuilder::visitType(AstType* t)
|
|||
return; // ok
|
||||
else if (auto s = t->as<AstTypeSingletonString>())
|
||||
return; // ok
|
||||
else if (auto g = t->as<AstTypeGroup>())
|
||||
return visitType(g->type);
|
||||
else
|
||||
handle->ice("Unknown AstType in DataFlowGraphBuilder::visitType");
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "Luau/Autocomplete.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/EqSatSimplification.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/Module.h"
|
||||
|
@ -19,16 +20,21 @@
|
|||
#include "Luau/Parser.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/Module.h"
|
||||
|
||||
#include "Luau/Clone.h"
|
||||
#include "AutocompleteCore.h"
|
||||
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTFLAG(LuauAllowFragmentParsing);
|
||||
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes)
|
||||
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
|
||||
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename T>
|
||||
|
@ -49,6 +55,96 @@ void copyModuleMap(Luau::DenseHashMap<K, V>& result, const Luau::DenseHashMap<K,
|
|||
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)
|
||||
{
|
||||
if (FFlag::LuauSolverV2 || !options)
|
||||
|
@ -265,13 +361,35 @@ std::optional<FragmentParseResult> parseFragment(
|
|||
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)
|
||||
{
|
||||
freeze(result->internalTypes);
|
||||
freeze(result->interfaceTypes);
|
||||
ModulePtr incrementalModule = std::make_shared<Module>();
|
||||
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);
|
||||
// Don't need to keep this alive (it's already on the source module)
|
||||
copyModuleVec(incrementalModule->scopes, result->scopes);
|
||||
|
@ -290,21 +408,6 @@ ModulePtr copyModule(const ModulePtr& result, std::unique_ptr<Allocator> alloc)
|
|||
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(
|
||||
const ScopePtr& bottomScopeStale,
|
||||
const ScopePtr& myFakeScope,
|
||||
|
@ -343,7 +446,9 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
{
|
||||
freeze(stale->internalTypes);
|
||||
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;
|
||||
unfreeze(incrementalModule->internalTypes);
|
||||
unfreeze(incrementalModule->interfaceTypes);
|
||||
|
@ -391,25 +496,34 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
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();
|
||||
// Any additions to the scope must occur in a fresh scope
|
||||
auto freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
|
||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||
|
||||
// Update freshChildOfNearestScope with the appropriate lvalueTypes
|
||||
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
|
||||
|
||||
// closest Scope -> children = { ...., freshChildOfNearestScope}
|
||||
// We need to trim nearestChild from the scope hierarcy
|
||||
closestScope->children.push_back(NotNull{freshChildOfNearestScope.get()});
|
||||
// Visit just the root - we know the scope it should be in
|
||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||
// Trim nearestChild from the closestScope
|
||||
Scope* back = closestScope->children.back().get();
|
||||
LUAU_ASSERT(back == freshChildOfNearestScope.get());
|
||||
closestScope->children.pop_back();
|
||||
|
||||
cloneAndSquashScopes(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
);
|
||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Any additions to the scope must occur in a fresh scope
|
||||
cg.rootScope = stale->getModuleScope().get();
|
||||
freshChildOfNearestScope = std::make_shared<Scope>(closestScope);
|
||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
|
||||
// closest Scope -> children = { ...., freshChildOfNearestScope}
|
||||
// We need to trim nearestChild from the scope hierarcy
|
||||
closestScope->children.emplace_back(freshChildOfNearestScope.get());
|
||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||
// 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
|
||||
ConstraintSolver cs{
|
||||
|
@ -458,6 +572,13 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
std::optional<Position> fragmentEndPosition
|
||||
)
|
||||
{
|
||||
|
||||
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||
{
|
||||
if (!frontend.allModuleDependenciesValid(moduleName, opts && opts->forAutocomplete))
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
}
|
||||
|
||||
const SourceModule* sourceModule = frontend.getSourceModule(moduleName);
|
||||
if (!sourceModule)
|
||||
{
|
||||
|
@ -473,6 +594,14 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
return {};
|
||||
}
|
||||
|
||||
if (FFlag::LuauIncrementalAutocompleteBugfixes && FFlag::LuauReferenceAllocatorInNewSolver)
|
||||
{
|
||||
if (sourceModule->allocator.get() != module->allocator.get())
|
||||
{
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
}
|
||||
}
|
||||
|
||||
auto tryParse = parseFragment(*sourceModule, src, cursorPos, fragmentEndPosition);
|
||||
|
||||
if (!tryParse)
|
||||
|
|
|
@ -47,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRunCustomModuleChecks, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterReverseDependencyTracking)
|
||||
|
||||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule)
|
||||
|
||||
|
@ -820,6 +822,16 @@ bool Frontend::parseGraph(
|
|||
topseen = Permanent;
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -1107,15 +1119,49 @@ void Frontend::recordItemResult(const BuildQueueItem& item)
|
|||
if (item.exception)
|
||||
std::rethrow_exception(item.exception);
|
||||
|
||||
if (item.options.forAutocomplete)
|
||||
if (FFlag::LuauBetterReverseDependencyTracking)
|
||||
{
|
||||
moduleResolverForAutocomplete.setModule(item.name, item.module);
|
||||
item.sourceNode->dirtyModuleForAutocomplete = false;
|
||||
bool replacedModule = 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
|
||||
{
|
||||
moduleResolver.setModule(item.name, item.module);
|
||||
item.sourceNode->dirtyModule = false;
|
||||
if (item.options.forAutocomplete)
|
||||
{
|
||||
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;
|
||||
|
@ -1152,6 +1198,13 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
|
|||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
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())
|
||||
|
@ -1186,22 +1303,10 @@ void Frontend::markDirty(const ModuleName& name, std::vector<ModuleName>* marked
|
|||
LUAU_ASSERT(sourceNodes.count(next) > 0);
|
||||
SourceNode& sourceNode = *sourceNodes[next];
|
||||
|
||||
if (markedDirty)
|
||||
markedDirty->push_back(next);
|
||||
|
||||
if (sourceNode.dirtySourceModule && sourceNode.dirtyModule && sourceNode.dirtyModuleForAutocomplete)
|
||||
if (!processSubtree(sourceNode))
|
||||
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];
|
||||
const Set<ModuleName>& dependents = sourceNode.dependents;
|
||||
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->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->requireLocations.clear();
|
||||
sourceNode->dirtySourceModule = false;
|
||||
|
@ -1764,11 +1880,21 @@ std::string FrontendModuleResolver::getHumanReadableModuleName(const 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);
|
||||
|
||||
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()
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -163,7 +164,7 @@ TypeId ReplaceGenerics::clean(TypeId ty)
|
|||
}
|
||||
else
|
||||
{
|
||||
return addType(FreeType{scope, level});
|
||||
return FFlag::LuauFreeTypesMustHaveBounds ? arena->freshType(builtinTypes, scope, level) : addType(FreeType{scope, level});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <iterator>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCountSelfCallsNonstrict)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -211,7 +212,7 @@ struct NonStrictTypeChecker
|
|||
return *fst;
|
||||
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});
|
||||
|
||||
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationTracksCyclicPairsThroughInhabitance)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNormalizedIntersectionOfNegatedClass)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -2284,9 +2284,24 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
|||
else if (isSubclass(there, hereTy))
|
||||
{
|
||||
TypeIds negations = std::move(hereNegations);
|
||||
bool emptyIntersectWithNegation = false;
|
||||
|
||||
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))
|
||||
{
|
||||
nIt = negations.erase(nIt);
|
||||
|
@ -2299,7 +2314,8 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
|||
|
||||
it = heres.ordering.erase(it);
|
||||
heres.classes.erase(hereTy);
|
||||
heres.pushPair(there, std::move(negations));
|
||||
if (!emptyIntersectWithNegation)
|
||||
heres.pushPair(there, std::move(negations));
|
||||
break;
|
||||
}
|
||||
// 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 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
|
||||
if (FFlag::LuauNormalizationTracksCyclicPairsThroughInhabitance)
|
||||
if (FFlag::LuauFixInfiniteRecursionInNormalization)
|
||||
{
|
||||
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 pair2 = std::pair{*tprop.readTy, *hprop.readTy};
|
||||
if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2))
|
||||
|
@ -2603,6 +2639,8 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
|||
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};
|
||||
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);
|
||||
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
|
||||
{
|
||||
|
|
|
@ -31,16 +31,16 @@ struct TypeSimplifier
|
|||
|
||||
int recursionDepth = 0;
|
||||
|
||||
TypeId mkNegation(TypeId ty);
|
||||
TypeId mkNegation(TypeId ty) const;
|
||||
|
||||
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 intersectNegatedUnion(TypeId unionTy, TypeId right);
|
||||
TypeId intersectNegatedUnion(TypeId left, TypeId right);
|
||||
|
||||
TypeId intersectTypeWithNegation(TypeId a, TypeId b);
|
||||
TypeId intersectNegations(TypeId a, TypeId b);
|
||||
TypeId intersectTypeWithNegation(TypeId left, TypeId right);
|
||||
TypeId intersectNegations(TypeId left, TypeId right);
|
||||
|
||||
TypeId intersectIntersectionWithType(TypeId left, TypeId right);
|
||||
|
||||
|
@ -48,8 +48,8 @@ struct TypeSimplifier
|
|||
// unions, intersections, or negations.
|
||||
std::optional<TypeId> basicIntersect(TypeId left, TypeId right);
|
||||
|
||||
TypeId intersect(TypeId ty, TypeId discriminant);
|
||||
TypeId union_(TypeId ty, TypeId discriminant);
|
||||
TypeId intersect(TypeId left, TypeId right);
|
||||
TypeId union_(TypeId left, TypeId right);
|
||||
|
||||
TypeId simplify(TypeId ty);
|
||||
TypeId simplify(TypeId ty, DenseHashSet<TypeId>& seen);
|
||||
|
@ -573,7 +573,7 @@ Relation relate(TypeId left, TypeId right)
|
|||
return relate(left, right, seen);
|
||||
}
|
||||
|
||||
TypeId TypeSimplifier::mkNegation(TypeId ty)
|
||||
TypeId TypeSimplifier::mkNegation(TypeId ty) const
|
||||
{
|
||||
TypeId result = nullptr;
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRetrySubtypingWithoutHiddenPack)
|
||||
|
||||
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.
|
||||
// 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);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,7 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
|||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -478,24 +479,12 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
|
|||
return false;
|
||||
}
|
||||
|
||||
FreeType::FreeType(TypeLevel level)
|
||||
// New constructors
|
||||
FreeType::FreeType(TypeLevel level, TypeId lowerBound, TypeId upperBound)
|
||||
: index(Unifiable::freshIndex())
|
||||
, level(level)
|
||||
, scope(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
FreeType::FreeType(Scope* scope)
|
||||
: index(Unifiable::freshIndex())
|
||||
, level{}
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
FreeType::FreeType(Scope* scope, TypeLevel level)
|
||||
: index(Unifiable::freshIndex())
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
, lowerBound(lowerBound)
|
||||
, upperBound(upperBound)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
: index(Unifiable::freshIndex())
|
||||
, name("g" + std::to_string(index))
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "Luau/TypeArena.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena);
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -22,7 +23,34 @@ TypeId TypeArena::addTV(Type&& tv)
|
|||
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});
|
||||
|
||||
|
@ -31,7 +59,7 @@ TypeId TypeArena::freshType(TypeLevel level)
|
|||
return allocated;
|
||||
}
|
||||
|
||||
TypeId TypeArena::freshType(Scope* scope)
|
||||
TypeId TypeArena::freshType_DEPRECATED(Scope* scope)
|
||||
{
|
||||
TypeId allocated = types.allocate(FreeType{scope});
|
||||
|
||||
|
@ -40,7 +68,7 @@ TypeId TypeArena::freshType(Scope* scope)
|
|||
return allocated;
|
||||
}
|
||||
|
||||
TypeId TypeArena::freshType(Scope* scope, TypeLevel level)
|
||||
TypeId TypeArena::freshType_DEPRECATED(Scope* scope, TypeLevel level)
|
||||
{
|
||||
TypeId allocated = types.allocate(FreeType{scope, level});
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
|
|||
|
||||
LUAU_FASTFLAG(InferGlobalTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableKeysAreRValues)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -2105,7 +2106,10 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
|
|||
}
|
||||
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));
|
||||
|
@ -2357,7 +2361,8 @@ TypeId TypeChecker2::flattenPack(TypePackId pack)
|
|||
return *fst;
|
||||
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});
|
||||
|
||||
TypePack* resultPack = emplaceTypePack<TypePack>(asMutable(pack));
|
||||
|
@ -2419,6 +2424,8 @@ void TypeChecker2::visit(AstType* ty)
|
|||
return visit(t);
|
||||
else if (auto t = ty->as<AstTypeIntersection>())
|
||||
return visit(t);
|
||||
else if (auto t = ty->as<AstTypeGroup>())
|
||||
return visit(t->type);
|
||||
}
|
||||
|
||||
void TypeChecker2::visit(AstTypeReference* ty)
|
||||
|
|
|
@ -47,7 +47,9 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauRemoveNotAnyHack)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotGeneralizeInTypeFunctions)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -825,7 +827,7 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
|||
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 (ctx->solver)
|
||||
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy, /* avoidSealingTables */ true);
|
||||
if (!maybeGeneralized)
|
||||
|
@ -917,7 +919,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
|||
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 (ctx->solver)
|
||||
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy);
|
||||
if (!maybeGeneralized)
|
||||
|
@ -1030,7 +1032,7 @@ std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunc
|
|||
AstStat* stmtArray[] = {&stmtReturn};
|
||||
AstArray<AstStat*> stmts{stmtArray, 1};
|
||||
AstStatBlock exec{Location{}, stmts};
|
||||
ParseResult parseResult{&exec, 1};
|
||||
ParseResult parseResult{&exec, 1, {}, {}, {}, CstNodeMap{nullptr}};
|
||||
|
||||
BytecodeBuilder builder;
|
||||
try
|
||||
|
@ -1160,7 +1162,7 @@ TypeFunctionReductionResult<TypeId> numericBinopTypeFunction(
|
|||
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 (ctx->solver)
|
||||
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
@ -1397,7 +1399,7 @@ TypeFunctionReductionResult<TypeId> concatTypeFunction(
|
|||
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 (ctx->solver)
|
||||
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
@ -1512,7 +1514,7 @@ TypeFunctionReductionResult<TypeId> andTypeFunction(
|
|||
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 (ctx->solver)
|
||||
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
@ -1567,7 +1569,7 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
|
|||
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 (ctx->solver)
|
||||
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
@ -1653,7 +1655,7 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
|
|||
rhsTy = follow(rhsTy);
|
||||
|
||||
// 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> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
@ -1791,7 +1793,7 @@ TypeFunctionReductionResult<TypeId> eqTypeFunction(
|
|||
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 (ctx->solver)
|
||||
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
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>>
|
||||
{
|
||||
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> discriminantMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, discriminant);
|
||||
|
@ -1988,16 +1990,8 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
*/
|
||||
if (auto nt = get<NegationType>(discriminant))
|
||||
{
|
||||
if (FFlag::LuauRemoveNotAnyHack)
|
||||
{
|
||||
if (get<NoRefineType>(follow(nt->ty)))
|
||||
return {target, {}};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (get<AnyType>(follow(nt->ty)))
|
||||
return {target, {}};
|
||||
}
|
||||
if (get<NoRefineType>(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
|
||||
|
@ -2070,7 +2064,7 @@ TypeFunctionReductionResult<TypeId> singletonTypeFunction(
|
|||
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 (ctx->solver)
|
||||
if (ctx->solver && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, type);
|
||||
if (!maybeGeneralized)
|
||||
|
@ -2091,6 +2085,43 @@ TypeFunctionReductionResult<TypeId> singletonTypeFunction(
|
|||
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(
|
||||
TypeId instance,
|
||||
const std::vector<TypeId>& typeParams,
|
||||
|
@ -2108,6 +2139,35 @@ TypeFunctionReductionResult<TypeId> unionTypeFunction(
|
|||
if (typeParams.size() == 1)
|
||||
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.
|
||||
std::vector<TypeId> types;
|
||||
types.reserve(typeParams.size());
|
||||
|
@ -2179,14 +2239,11 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
|
|||
for (auto ty : typeParams)
|
||||
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]))
|
||||
return {types[0], Reduction::MaybeOk, {}, {}};
|
||||
else if (types.size() == 2 && get<NoRefineType>(types[0]))
|
||||
return {types[1], Reduction::MaybeOk, {}, {}};
|
||||
}
|
||||
// if we only have two parameters and one is `*no-refine*`, we're all done.
|
||||
if (types.size() == 2 && get<NoRefineType>(types[1]))
|
||||
return {types[0], 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
|
||||
// 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)
|
||||
{
|
||||
// skip any `*no-refine*` types.
|
||||
if (FFlag::LuauRemoveNotAnyHack && get<NoRefineType>(ty))
|
||||
if (get<NoRefineType>(ty))
|
||||
continue;
|
||||
|
||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
|
||||
|
@ -2722,6 +2779,215 @@ TypeFunctionReductionResult<TypeId> rawgetTypeFunction(
|
|||
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()
|
||||
: userFunc{"user", userDefinedTypeFunction}
|
||||
, notFunc{"not", notTypeFunction}
|
||||
|
@ -2748,6 +3014,8 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
|
|||
, rawkeyofFunc{"rawkeyof", rawkeyofTypeFunction}
|
||||
, indexFunc{"index", indexTypeFunction}
|
||||
, 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[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc);
|
||||
|
||||
if (FFlag::LuauMetatableTypeFunctions)
|
||||
{
|
||||
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc);
|
||||
scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc);
|
||||
}
|
||||
}
|
||||
|
||||
const BuiltinTypeFunctions& builtinTypeFunctions()
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixInner)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixNoReadWrite)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunGenerics)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunCloneTail)
|
||||
|
||||
|
@ -683,10 +682,8 @@ static int readTableProp(lua_State* L)
|
|||
auto prop = tftt->props.at(tfsst->value);
|
||||
if (prop.readTy)
|
||||
allocTypeUserData(L, (*prop.readTy)->type);
|
||||
else if (FFlag::LuauUserTypeFunFixNoReadWrite)
|
||||
lua_pushnil(L);
|
||||
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;
|
||||
}
|
||||
|
@ -723,10 +720,8 @@ static int writeTableProp(lua_State* L)
|
|||
auto prop = tftt->props.at(tfsst->value);
|
||||
if (prop.writeTy)
|
||||
allocTypeUserData(L, (*prop.writeTy)->type);
|
||||
else if (FFlag::LuauUserTypeFunFixNoReadWrite)
|
||||
lua_pushnil(L);
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOldSolverCreatesChildScopePointers)
|
||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -761,8 +763,12 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatRepeat& state
|
|||
|
||||
struct Demoter : Substitution
|
||||
{
|
||||
Demoter(TypeArena* arena)
|
||||
TypeArena* arena = nullptr;
|
||||
NotNull<BuiltinTypes> builtins;
|
||||
Demoter(TypeArena* arena, NotNull<BuiltinTypes> builtins)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, arena(arena)
|
||||
, builtins(builtins)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -788,7 +794,8 @@ struct Demoter : Substitution
|
|||
{
|
||||
auto ftv = get<FreeType>(ty);
|
||||
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
|
||||
|
@ -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);
|
||||
|
||||
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);
|
||||
|
||||
return expectedTypes;
|
||||
|
@ -5273,7 +5280,8 @@ TypeId TypeChecker::freshType(const ScopePtr& scope)
|
|||
|
||||
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)
|
||||
|
@ -5718,6 +5726,12 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
|
|||
}
|
||||
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;
|
||||
for (AstType* ann : un->types)
|
||||
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>())
|
||||
{
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
{
|
||||
if (un->types.size == 1)
|
||||
return resolveType(scope, *un->types.data[0]);
|
||||
}
|
||||
|
||||
std::vector<TypeId> types;
|
||||
for (AstType* ann : un->types)
|
||||
types.push_back(resolveType(scope, *ann));
|
||||
|
||||
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>())
|
||||
{
|
||||
return singletonType(tsb->value);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -12,6 +13,7 @@
|
|||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete);
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope);
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -323,7 +325,7 @@ TypePack extendTypePack(
|
|||
trackInteriorFreeType(ftp->scope, t);
|
||||
}
|
||||
else
|
||||
t = arena.freshType(ftp->scope);
|
||||
t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope);
|
||||
}
|
||||
|
||||
newPack.head.push_back(t);
|
||||
|
|
|
@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart)
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1648,7 +1649,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
if (FFlag::LuauSolverV2)
|
||||
return freshType(NotNull{types}, builtinTypes, scope);
|
||||
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});
|
||||
|
|
|
@ -1204,6 +1204,18 @@ public:
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
@ -1470,6 +1482,10 @@ public:
|
|||
{
|
||||
return visit(static_cast<AstType*>(node));
|
||||
}
|
||||
virtual bool visit(class AstTypeGroup* node)
|
||||
{
|
||||
return visit(static_cast<AstType*>(node));
|
||||
}
|
||||
virtual bool visit(class AstTypeError* 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
|
||||
};
|
||||
|
||||
enum struct QuoteStyle
|
||||
{
|
||||
Single,
|
||||
Double,
|
||||
};
|
||||
|
||||
Type type;
|
||||
Location location;
|
||||
|
||||
|
@ -111,6 +117,8 @@ public:
|
|||
Lexeme(const Location& location, Type type, const char* name);
|
||||
|
||||
unsigned int getLength() const;
|
||||
unsigned int getBlockDepth() const;
|
||||
QuoteStyle getQuoteStyle() const;
|
||||
|
||||
std::string toString() const;
|
||||
};
|
||||
|
|
|
@ -29,6 +29,8 @@ struct ParseOptions
|
|||
bool allowDeclarationSyntax = false;
|
||||
bool captureComments = false;
|
||||
std::optional<FragmentParseResumeSettings> parseFragment = std::nullopt;
|
||||
bool storeCstData = false;
|
||||
bool noErrorLimit = false;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Luau
|
|||
{
|
||||
|
||||
class AstStatBlock;
|
||||
class CstNode;
|
||||
|
||||
class ParseError : public std::exception
|
||||
{
|
||||
|
@ -55,6 +56,8 @@ struct Comment
|
|||
Location location;
|
||||
};
|
||||
|
||||
using CstNodeMap = DenseHashMap<AstNode*, CstNode*>;
|
||||
|
||||
struct ParseResult
|
||||
{
|
||||
AstStatBlock* root;
|
||||
|
@ -64,6 +67,8 @@ struct ParseResult
|
|||
std::vector<ParseError> errors;
|
||||
|
||||
std::vector<Comment> commentLocations;
|
||||
|
||||
CstNodeMap cstNodeMap{nullptr};
|
||||
};
|
||||
|
||||
static constexpr const char* kParseNameError = "%error-id%";
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Cst.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
|
@ -173,14 +174,18 @@ private:
|
|||
);
|
||||
|
||||
// explist ::= {exp `,'} exp
|
||||
void parseExprList(TempVector<AstExpr*>& result);
|
||||
void parseExprList(TempVector<AstExpr*>& result, TempVector<Position>* commaPositions = nullptr);
|
||||
|
||||
// binding ::= Name [`:` Type]
|
||||
Binding parseBinding();
|
||||
|
||||
// bindinglist ::= (binding | `...') {`,' bindinglist}
|
||||
// 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();
|
||||
|
||||
|
@ -201,7 +206,17 @@ private:
|
|||
std::optional<AstTypeList> parseOptionalReturnType();
|
||||
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);
|
||||
AstType* parseFunctionTypeTail(
|
||||
|
@ -259,6 +274,8 @@ private:
|
|||
// args ::= `(' [explist] `)' | tableconstructor | String
|
||||
AstExpr* parseFunctionArgs(AstExpr* func, bool self);
|
||||
|
||||
std::optional<CstExprTable::Separator> tableSeparator();
|
||||
|
||||
// tableconstructor ::= `{' [fieldlist] `}'
|
||||
// fieldlist ::= field {fieldsep field} [fieldsep]
|
||||
// field ::= `[' exp `]' `=' exp | Name `=' exp | exp
|
||||
|
@ -280,9 +297,13 @@ private:
|
|||
std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> parseGenericTypeList(bool withDefaultValues);
|
||||
|
||||
// `<' 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* parseNumber();
|
||||
|
||||
|
@ -292,6 +313,9 @@ private:
|
|||
|
||||
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
|
||||
bool expectAndConsume(char value, const char* context = nullptr);
|
||||
bool expectAndConsume(Lexeme::Type type, const char* context = nullptr);
|
||||
|
@ -435,6 +459,7 @@ private:
|
|||
std::vector<AstAttr*> scratchAttr;
|
||||
std::vector<AstStat*> scratchStat;
|
||||
std::vector<AstArray<char>> scratchString;
|
||||
std::vector<AstArray<char>> scratchString2;
|
||||
std::vector<AstExpr*> scratchExpr;
|
||||
std::vector<AstExpr*> scratchExprAux;
|
||||
std::vector<AstName> scratchName;
|
||||
|
@ -442,15 +467,20 @@ private:
|
|||
std::vector<Binding> scratchBinding;
|
||||
std::vector<AstLocal*> scratchLocal;
|
||||
std::vector<AstTableProp> scratchTableTypeProps;
|
||||
std::vector<CstTypeTable::Item> scratchCstTableTypeProps;
|
||||
std::vector<AstType*> scratchType;
|
||||
std::vector<AstTypeOrPack> scratchTypeOrPack;
|
||||
std::vector<AstDeclaredClassProp> scratchDeclaredClassProps;
|
||||
std::vector<AstExprTable::Item> scratchItem;
|
||||
std::vector<CstExprTable::Item> scratchCstItem;
|
||||
std::vector<AstArgumentName> scratchArgName;
|
||||
std::vector<AstGenericType> scratchGenericTypes;
|
||||
std::vector<AstGenericTypePack> scratchGenericTypePacks;
|
||||
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
||||
std::vector<Position> scratchPosition;
|
||||
std::string scratchData;
|
||||
|
||||
CstNodeMap cstNodeMap;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -1091,6 +1091,18 @@ void AstTypeSingletonString::visit(AstVisitor* visitor)
|
|||
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)
|
||||
: AstType(ClassIndex(), location)
|
||||
, 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)
|
||||
: buffer(buffer)
|
||||
, bufferSize(bufferSize)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,9 +20,21 @@
|
|||
|
||||
#elif defined(__linux__) || defined(__APPLE__)
|
||||
|
||||
// Defined in unwind.h which may not be easily discoverable on various platforms
|
||||
extern "C" void __register_frame(const void*) __attribute__((weak));
|
||||
extern "C" void __deregister_frame(const void*) __attribute__((weak));
|
||||
// __register_frame and __deregister_frame are defined in libgcc or libc++
|
||||
// (depending on how it's built). We want to declare them as weak symbols
|
||||
// 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));
|
||||
#endif
|
||||
|
@ -121,7 +133,7 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
|
|||
#endif
|
||||
|
||||
#elif defined(__linux__) || defined(__APPLE__)
|
||||
if (!__register_frame)
|
||||
if (!&__register_frame)
|
||||
return nullptr;
|
||||
|
||||
visitFdeEntries(unwindData, __register_frame);
|
||||
|
@ -150,7 +162,7 @@ void destroyBlockUnwindInfo(void* context, void* unwindData)
|
|||
#endif
|
||||
|
||||
#elif defined(__linux__) || defined(__APPLE__)
|
||||
if (!__deregister_frame)
|
||||
if (!&__deregister_frame)
|
||||
{
|
||||
CODEGEN_ASSERT(!"Cannot deregister unwind information");
|
||||
return;
|
||||
|
|
|
@ -14,7 +14,7 @@ inline bool isFlagExperimental(const char* flag)
|
|||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
||||
"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",
|
||||
// makes sure we always have at least one entry
|
||||
nullptr,
|
||||
|
|
|
@ -121,6 +121,10 @@ static LuauBytecodeType getType(
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ target_sources(Luau.Ast PRIVATE
|
|||
Ast/include/Luau/Allocator.h
|
||||
Ast/include/Luau/Ast.h
|
||||
Ast/include/Luau/Confusables.h
|
||||
Ast/include/Luau/Cst.h
|
||||
Ast/include/Luau/Lexer.h
|
||||
Ast/include/Luau/Location.h
|
||||
Ast/include/Luau/ParseOptions.h
|
||||
|
@ -28,6 +29,7 @@ target_sources(Luau.Ast PRIVATE
|
|||
Ast/src/Allocator.cpp
|
||||
Ast/src/Ast.cpp
|
||||
Ast/src/Confusables.cpp
|
||||
Ast/src/Cst.cpp
|
||||
Ast/src/Lexer.cpp
|
||||
Ast/src/Location.cpp
|
||||
Ast/src/Parser.cpp
|
||||
|
|
|
@ -270,7 +270,7 @@ static int buffer_readbits(lua_State* L)
|
|||
|
||||
uint64_t data = 0;
|
||||
|
||||
#if LUAU_BIG_ENDIAN
|
||||
#if defined(LUAU_BIG_ENDIAN)
|
||||
for (int i = int(endbyte) - 1; i >= int(startbyte); i--)
|
||||
data = (data << 8) + uint8_t(((char*)buf)[i]);
|
||||
#else
|
||||
|
@ -306,7 +306,7 @@ static int buffer_writebits(lua_State* L)
|
|||
|
||||
uint64_t data = 0;
|
||||
|
||||
#if LUAU_BIG_ENDIAN
|
||||
#if defined(LUAU_BIG_ENDIAN)
|
||||
for (int i = int(endbyte) - 1; i >= int(startbyte); i--)
|
||||
data = data * 256 + uint8_t(((char*)buf)[i]);
|
||||
#else
|
||||
|
@ -318,7 +318,7 @@ static int buffer_writebits(lua_State* L)
|
|||
|
||||
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++)
|
||||
{
|
||||
((char*)buf)[i] = data & 0xff;
|
||||
|
|
|
@ -20,7 +20,8 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
|
|||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes)
|
||||
LUAU_FASTFLAG(LuauRemoveNotAnyHack)
|
||||
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup)
|
||||
|
||||
|
||||
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[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")
|
||||
|
@ -413,7 +429,6 @@ TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::StudioReportLuauAny2, true},
|
||||
{FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true},
|
||||
{FFlag::LuauRemoveNotAnyHack, true},
|
||||
};
|
||||
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
|
@ -507,7 +522,6 @@ TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::StudioReportLuauAny2, true},
|
||||
{FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true},
|
||||
{FFlag::LuauRemoveNotAnyHack, true},
|
||||
};
|
||||
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
|
@ -577,13 +591,26 @@ initialize()
|
|||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
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"
|
||||
);
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
CHECK_EQ(
|
||||
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"
|
||||
);
|
||||
}
|
||||
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")
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup)
|
||||
|
||||
struct JsonEncoderFixture
|
||||
{
|
||||
Allocator allocator;
|
||||
|
@ -471,10 +473,17 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation")
|
|||
{
|
||||
AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())");
|
||||
|
||||
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);
|
||||
if (FFlag::LuauAstTypeGroup)
|
||||
{
|
||||
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);
|
||||
}
|
||||
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")
|
||||
|
|
|
@ -4329,4 +4329,21 @@ local var = data;@1
|
|||
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();
|
||||
|
|
|
@ -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")
|
||||
{
|
||||
// we can fold math.pi at optimization level 2
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "Fixture.h"
|
||||
|
||||
#include "Luau/EqSatSimplification.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -76,7 +77,7 @@ TEST_CASE_FIXTURE(ESFixture, "number | string")
|
|||
|
||||
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});
|
||||
|
||||
CHECK("number" == simplifyStr(ty));
|
||||
|
@ -450,7 +451,7 @@ TEST_CASE_FIXTURE(ESFixture, "(boolean | nil) & (false | nil)")
|
|||
TEST_CASE_FIXTURE(ESFixture, "free & string & number")
|
||||
{
|
||||
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}})));
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "Luau/Common.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/AutocompleteTypes.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
@ -27,6 +28,14 @@ LUAU_FASTFLAG(LuauStoreSolverTypeOnModule);
|
|||
LUAU_FASTFLAG(LexerResumesFromPosition2)
|
||||
LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
|
||||
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)
|
||||
{
|
||||
|
@ -46,15 +55,25 @@ static FrontendOptions getOptions()
|
|||
return options;
|
||||
}
|
||||
|
||||
static ModuleResolver& getModuleResolver(Luau::Frontend& frontend)
|
||||
{
|
||||
return FFlag::LuauSolverV2 ? frontend.moduleResolver : frontend.moduleResolverForAutocomplete;
|
||||
}
|
||||
|
||||
template<class 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::LuauAutocompleteRefactorsForIncrementalAutocomplete, true},
|
||||
{FFlag::LuauStoreSolverTypeOnModule, true},
|
||||
{FFlag::LuauSymbolEquality, true},
|
||||
{FFlag::LexerResumesFromPosition2, true}
|
||||
{FFlag::LexerResumesFromPosition2, true},
|
||||
{FFlag::LuauReferenceAllocatorInNewSolver, true},
|
||||
{FFlag::LuauIncrementalAutocompleteBugfixes, true},
|
||||
{FFlag::LuauBetterReverseDependencyTracking, true},
|
||||
};
|
||||
|
||||
FragmentAutocompleteFixtureImpl()
|
||||
|
@ -128,6 +147,26 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
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>
|
||||
|
@ -162,10 +201,13 @@ end
|
|||
// 'for autocomplete'.
|
||||
loadDefinition(fakeVecDecl);
|
||||
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_CASE_FIXTURE(FragmentAutocompleteFixture, "just_two_locals")
|
||||
|
@ -574,7 +616,7 @@ t
|
|||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
//NOLINTEND(bugprone-unchecked-optional-access)
|
||||
// NOLINTEND(bugprone-unchecked-optional-access)
|
||||
|
||||
TEST_SUITE_BEGIN("FragmentAutocompleteTypeCheckerTests");
|
||||
|
||||
|
@ -710,6 +752,57 @@ tbl.
|
|||
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_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();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// 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/BuiltinDefinitions.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/RequireTracer.h"
|
||||
|
||||
|
@ -17,6 +18,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena);
|
|||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver);
|
||||
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
|
||||
LUAU_FASTFLAG(LuauBetterReverseDependencyTracking);
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -1572,4 +1574,207 @@ return {x = a, y = b, z = c}
|
|||
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();
|
||||
|
|
|
@ -179,9 +179,9 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "functions_containing_cyclic_tables_can
|
|||
TEST_CASE_FIXTURE(GeneralizationFixture, "union_type_traversal_doesnt_crash")
|
||||
{
|
||||
// t1 where t1 = ('h <: (t1 <: 'i)) | ('j <: (t1 <: 'i))
|
||||
TypeId i = arena.addType(FreeType{NotNull{globalScope.get()}});
|
||||
TypeId h = arena.addType(FreeType{NotNull{globalScope.get()}});
|
||||
TypeId j = arena.addType(FreeType{NotNull{globalScope.get()}});
|
||||
TypeId i = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||
TypeId h = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||
TypeId j = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||
TypeId unionType = arena.addType(UnionType{{h, j}});
|
||||
getMutable<FreeType>(h)->upperBound = i;
|
||||
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")
|
||||
{
|
||||
// t1 where t1 = ('h <: (t1 <: 'i)) & ('j <: (t1 <: 'i))
|
||||
TypeId i = arena.addType(FreeType{NotNull{globalScope.get()}});
|
||||
TypeId h = arena.addType(FreeType{NotNull{globalScope.get()}});
|
||||
TypeId j = arena.addType(FreeType{NotNull{globalScope.get()}});
|
||||
TypeId i = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||
TypeId h = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||
TypeId j = arena.freshType(NotNull{&builtinTypes}, globalScope.get());
|
||||
TypeId intersectionType = arena.addType(IntersectionType{{h, j}});
|
||||
|
||||
getMutable<FreeType>(h)->upperBound = i;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "Fixture.h"
|
||||
#include "ClassFixture.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "ScopedFlags.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
@ -29,7 +30,7 @@ TEST_CASE_FIXTURE(Fixture, "weird_cyclic_instantiation")
|
|||
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
||||
|
||||
TypeId freeTy = arena.freshType(&scope);
|
||||
TypeId freeTy = arena.freshType(builtinTypes, &scope);
|
||||
FreeType* ft = getMutable<FreeType>(freeTy);
|
||||
REQUIRE(ft);
|
||||
ft->lowerBound = idTy;
|
||||
|
|
|
@ -248,4 +248,185 @@ TEST_CASE("string_interpolation_with_unicode_escape")
|
|||
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();
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauNormalizationTracksCyclicPairsThroughInhabitance)
|
||||
LUAU_FASTFLAG(LuauFixNormalizedIntersectionOfNegatedClass)
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
|
@ -851,17 +851,17 @@ TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable")
|
|||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauFixNormalizedIntersectionOfNegatedClass, true};
|
||||
createSomeClasses(&frontend);
|
||||
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("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("(boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<cls>")));
|
||||
CHECK(
|
||||
"(Parent | Unrelated | boolean | buffer | function | number | string | table | thread)?" ==
|
||||
toString(normal("Not<cls & Not<Parent> & Not<Child> & 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")
|
||||
{
|
||||
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> na2 = normalizer.normalize(a);
|
||||
|
@ -1034,7 +1034,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_t
|
|||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
|
||||
ScopedFastFlag sff{FFlag::LuauNormalizationTracksCyclicPairsThroughInhabitance, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes)
|
|||
LUAU_FASTFLAG(LuauErrorRecoveryForClassNames)
|
||||
LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
|
||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup)
|
||||
|
||||
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>();
|
||||
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>());
|
||||
}
|
||||
|
||||
|
@ -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'");
|
||||
}
|
||||
|
||||
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_BEGIN("ParseErrorRecovery");
|
||||
|
@ -3688,7 +3778,14 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
|
|||
auto unionTy = paramTy.type->as<AstTypeUnion>();
|
||||
LUAU_ASSERT(unionTy);
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -324,8 +324,7 @@ TEST_CASE_FIXTURE(Fixture, "free")
|
|||
{
|
||||
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;
|
||||
opts.showPointers = false;
|
||||
CHECK_EQ(
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,6 +14,7 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||
LUAU_FASTFLAG(LuauMetatableTypeFunctions)
|
||||
|
||||
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();
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunFixNoReadWrite)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunFixInner)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunGenerics)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunCloneTail)
|
||||
|
@ -667,7 +666,6 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works")
|
|||
TEST_CASE_FIXTURE(ClassFixture, "write_of_readonly_is_nil")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag udtfRwFix{FFlag::LuauUserTypeFunFixNoReadWrite, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function getclass(arg)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "ScopedFlags.h"
|
||||
#include "doctest.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/AstQuery.h"
|
||||
|
@ -9,6 +10,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
|
||||
|
||||
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();
|
||||
|
|
|
@ -10,10 +10,9 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
||||
LUAU_FASTFLAG(LuauStringFormatArityFix)
|
||||
LUAU_FASTFLAG(LuauTableCloneClonesType2)
|
||||
LUAU_FASTFLAG(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
|
||||
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
|
||||
|
||||
TEST_SUITE_BEGIN("BuiltinTests");
|
||||
|
||||
|
@ -809,8 +808,6 @@ TEST_CASE_FIXTURE(Fixture, "string_format_as_method")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_trivial_arity")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauStringFormatArityFix, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
string.format()
|
||||
)");
|
||||
|
@ -1134,15 +1131,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
|
|||
)");
|
||||
|
||||
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]));
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK("Key 'b' not found in table '{ a: number }'" == toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ("Key 'b' not found in table '{| a: number |}'", toString(result.errors[0]));
|
||||
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 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);
|
||||
|
||||
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}));
|
||||
// before the assignment, it's `t1`
|
||||
|
@ -1208,8 +1203,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_no_generic_table")
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauTypestateBuiltins2)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_on_metatable")
|
||||
|
@ -1236,13 +1230,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_errors_on_no_args")
|
|||
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);
|
||||
|
||||
CHECK(get<CountMismatch>(result.errors[0]));
|
||||
|
@ -1255,25 +1242,40 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_errors_on_non_tables")
|
|||
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);
|
||||
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauTypestateBuiltins2)
|
||||
if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ(toString(tm->wantedType), "table");
|
||||
else
|
||||
CHECK_EQ(toString(tm->wantedType), "{- -}");
|
||||
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")
|
||||
{
|
||||
// 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);
|
||||
|
||||
if (FFlag::LuauTableCloneClonesType2)
|
||||
if (FFlag::LuauTableCloneClonesType3)
|
||||
{
|
||||
CHECK_EQ(toString(requireType("t1"), {true}), "{ x: number, z: 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")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauNewSolverPrePopulateClasses)
|
||||
LUAU_FASTFLAG(LuauClipNestedAndRecursiveUnion)
|
||||
|
||||
TEST_SUITE_BEGIN("DefinitionTests");
|
||||
|
||||
|
@ -541,4 +542,20 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")
|
|||
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();
|
||||
|
|
|
@ -19,7 +19,6 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTFLAG(LuauRetrySubtypingWithoutHiddenPack)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
|
||||
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")
|
||||
{
|
||||
// Only applies to new solver.
|
||||
ScopedFastFlag sff{FFlag::LuauRetrySubtypingWithoutHiddenPack, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
type FooType = {
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
||||
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
|
||||
|
||||
using namespace Luau;
|
||||
|
@ -185,10 +184,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_table_freeze")
|
|||
ModulePtr b = frontend.moduleResolver.getModule("game/B");
|
||||
REQUIRE(b != nullptr);
|
||||
// confirm that no cross-module mutation happened here!
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauTypestateBuiltins2)
|
||||
if (FFlag::LuauSolverV2)
|
||||
CHECK(toString(b->returnType) == "{ read a: number }");
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK(toString(b->returnType) == "{ a: number }");
|
||||
else
|
||||
CHECK(toString(b->returnType) == "{| a: number |}");
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauDoNotGeneralizeInTypeFunctions)
|
||||
|
||||
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")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local t = {}
|
||||
while true and t[1] do
|
||||
print(t[1].test)
|
||||
end
|
||||
)");
|
||||
ScopedFastFlag _{FFlag::LuauDoNotGeneralizeInTypeFunctions, true};
|
||||
|
||||
// This infers a type for `t` of `{unknown}`, and so it makes sense that `t[1].test` would error.
|
||||
if (FFlag::LuauSolverV2)
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
else
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
// `t` will be inferred to be of type `{ { test: unknown } }` which is
|
||||
// reasonable, in that it's empty with no bounds on its members. Optimally
|
||||
// we might emit an error here that the `print(...)` expression is
|
||||
// unreachable.
|
||||
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")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// 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/RecursionCounter.h"
|
||||
|
||||
|
@ -12,6 +13,7 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification);
|
||||
LUAU_FASTFLAG(LuauStoreCSTData);
|
||||
LUAU_FASTINT(LuauNormalizeCacheLimit);
|
||||
LUAU_FASTINT(LuauTarjanChildLimit);
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||
|
@ -46,7 +48,16 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
|||
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...)}): ()
|
||||
if type(a) == 'boolean'then
|
||||
local a1:boolean=a
|
||||
|
@ -56,7 +67,16 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
|||
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)}): ()
|
||||
if type(a) == 'boolean'then
|
||||
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
||||
|
@ -66,7 +86,16 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
|||
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)}): ()
|
||||
if type(a) == 'boolean'then
|
||||
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);
|
||||
|
||||
TypeId free1 = arena.addType(FreeType{scope.get()});
|
||||
TypeId free1 = arena.freshType(builtinTypes, scope.get());
|
||||
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}});
|
||||
|
||||
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);
|
||||
|
||||
TypeId free1 = arena.addType(FreeType{scope.get()});
|
||||
TypeId free1 = arena.freshType(builtinTypes, scope.get());
|
||||
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}});
|
||||
|
||||
InternalErrorReporter iceHandler;
|
||||
|
@ -1284,7 +1313,7 @@ TEST_CASE_FIXTURE(Fixture, "table_containing_non_final_type_is_erroneously_cache
|
|||
TableType* table = getMutable<TableType>(tableTy);
|
||||
REQUIRE(table);
|
||||
|
||||
TypeId freeTy = arena.freshType(&globalScope);
|
||||
TypeId freeTy = arena.freshType(builtinTypes, &globalScope);
|
||||
|
||||
table->props["foo"] = Property::rw(freeTy);
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
||||
LUAU_FASTFLAG(LuauRetrySubtypingWithoutHiddenPack)
|
||||
LUAU_FASTFLAG(LuauTableKeysAreRValues)
|
||||
LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
|
@ -2378,7 +2377,7 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
|||
local c : string = t.m("hi")
|
||||
)");
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauRetrySubtypingWithoutHiddenPack)
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
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.
|
||||
// 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
|
||||
else if (FFlag::LuauInstantiateInSubtyping)
|
||||
|
|
|
@ -24,6 +24,7 @@ LUAU_FASTINT(LuauNormalizeCacheLimit);
|
|||
LUAU_FASTINT(LuauRecursionLimit);
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
||||
LUAU_FASTFLAG(InferGlobalTypes)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1201,7 +1202,10 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
|
|||
CHECK(3 == result.errors.size());
|
||||
CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].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[1]));
|
||||
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")
|
||||
{
|
||||
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{
|
||||
TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({arena.freshType(globalScope->level)}))}
|
||||
};
|
||||
Type functionTwo{TypeVariant{FunctionType(
|
||||
arena.addTypePack({arena.freshType(builtinTypes, globalScope->level)}), arena.addTypePack({arena.freshType(builtinTypes, globalScope->level)})
|
||||
)}};
|
||||
|
||||
state.tryUnify(&functionTwo, &functionOne);
|
||||
CHECK(!state.failure);
|
||||
|
@ -60,14 +61,16 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified")
|
|||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_functions_are_preserved")
|
||||
{
|
||||
TypePackVar argPackOne{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}};
|
||||
Type functionOne{TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))
|
||||
TypePackVar argPackOne{TypePack{{arena.freshType(builtinTypes, globalScope->level)}, std::nullopt}};
|
||||
Type functionOne{TypeVariant{
|
||||
FunctionType(arena.addTypePack({arena.freshType(builtinTypes, globalScope->level)}), arena.addTypePack({builtinTypes->numberType}))
|
||||
}};
|
||||
|
||||
Type functionOneSaved = functionOne.clone();
|
||||
|
||||
TypePackVar argPackTwo{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}};
|
||||
Type functionTwo{TypeVariant{FunctionType(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({builtinTypes->stringType}))
|
||||
TypePackVar argPackTwo{TypePack{{arena.freshType(builtinTypes, globalScope->level)}, std::nullopt}};
|
||||
Type functionTwo{TypeVariant{
|
||||
FunctionType(arena.addTypePack({arena.freshType(builtinTypes, globalScope->level)}), arena.addTypePack({builtinTypes->stringType}))
|
||||
}};
|
||||
|
||||
Type functionTwoSaved = functionTwo.clone();
|
||||
|
@ -83,11 +86,11 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_functions_are_preserved")
|
|||
TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified")
|
||||
{
|
||||
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{
|
||||
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());
|
||||
|
@ -106,7 +109,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
|
|||
{
|
||||
Type tableOne{TypeVariant{
|
||||
TableType{
|
||||
{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->numberType}}},
|
||||
{{"foo", {arena.freshType(builtinTypes, globalScope->level)}}, {"bar", {builtinTypes->numberType}}},
|
||||
std::nullopt,
|
||||
globalScope->level,
|
||||
TableState::Unsealed
|
||||
|
@ -115,7 +118,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
|
|||
|
||||
Type tableTwo{TypeVariant{
|
||||
TableType{
|
||||
{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {builtinTypes->stringType}}},
|
||||
{{"foo", {arena.freshType(builtinTypes, globalScope->level)}}, {"bar", {builtinTypes->stringType}}},
|
||||
std::nullopt,
|
||||
globalScope->level,
|
||||
TableState::Unsealed
|
||||
|
@ -295,7 +298,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly")
|
|||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag")
|
||||
{
|
||||
Type redirect{FreeType{TypeLevel{}}};
|
||||
Type redirect{FreeType{TypeLevel{}, builtinTypes->neverType, builtinTypes->unknownType}};
|
||||
Type table{TableType{}};
|
||||
Type metatable{MetatableType{&redirect, &table}};
|
||||
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")
|
||||
{
|
||||
TypeId a = arena.addType(Type{FreeType{TypeLevel{}}});
|
||||
TypeId a = arena.freshType(builtinTypes, TypeLevel{});
|
||||
TypeId b = builtinTypes->numberType;
|
||||
|
||||
state.tryUnify(a, b);
|
||||
|
@ -381,7 +384,7 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
|
|||
TypePackVar packTmp{TypePack{{builtinTypes->anyType}, &variadicAny}};
|
||||
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 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> nestedScope = std::make_shared<Scope>(scope);
|
||||
|
||||
const TypeId outerType = arena.freshType(scope.get());
|
||||
const TypeId outerType2 = arena.freshType(scope.get());
|
||||
const TypeId outerType = arena.freshType(builtinTypes, 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();
|
||||
|
||||
|
|
|
@ -621,7 +621,7 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
|
|||
TypeArena& arena = frontend.globals.globalTypes;
|
||||
unfreeze(arena);
|
||||
|
||||
TypeId badCyclicUnionTy = arena.freshType(frontend.globals.globalScope.get());
|
||||
TypeId badCyclicUnionTy = arena.freshType(builtinTypes, frontend.globals.globalScope.get());
|
||||
UnionType u;
|
||||
|
||||
u.options.push_back(badCyclicUnionTy);
|
||||
|
|
|
@ -17,6 +17,7 @@ using namespace Luau::TypePath;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds);
|
||||
|
||||
struct TypePathFixture : Fixture
|
||||
{
|
||||
|
@ -277,7 +278,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "bounds")
|
|||
TypeArena& arena = frontend.globals.globalTypes;
|
||||
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);
|
||||
|
||||
SUBCASE("upper")
|
||||
|
|
|
@ -219,7 +219,7 @@ TEST_CASE_FIXTURE(Fixture, "UnionTypeIterator_with_only_cyclic_union")
|
|||
*/
|
||||
TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure")
|
||||
{
|
||||
Type ftv11{FreeType{TypeLevel{}}};
|
||||
Type ftv11{FreeType{TypeLevel{}, builtinTypes->neverType, builtinTypes->unknownType}};
|
||||
|
||||
TypePackVar tp24{TypePack{{&ftv11}}};
|
||||
TypePackVar tp17{TypePack{}};
|
||||
|
@ -469,8 +469,8 @@ TEST_CASE("content_reassignment")
|
|||
myAny.documentationSymbol = "@global/any";
|
||||
|
||||
TypeArena arena;
|
||||
|
||||
TypeId futureAny = arena.addType(FreeType{TypeLevel{}});
|
||||
BuiltinTypes builtinTypes;
|
||||
TypeId futureAny = arena.freshType(NotNull{&builtinTypes}, TypeLevel{});
|
||||
asMutable(futureAny)->reassign(myAny);
|
||||
|
||||
CHECK(get<AnyType>(futureAny) != nullptr);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "Luau/RecursionCounter.h"
|
||||
|
||||
#include "Luau/Type.h"
|
||||
#include "doctest.h"
|
||||
|
||||
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")
|
||||
{
|
||||
Type t{FreeType{TypeLevel{}}};
|
||||
Type t{FreeType{TypeLevel{}, builtinTypes->neverType, builtinTypes->unknownType}};
|
||||
|
||||
(void)toString(&t);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue