mirror of
https://github.com/luau-lang/luau.git
synced 2025-04-01 17:30:53 +01:00
Sync to upstream/release/666
This commit is contained in:
parent
af2025306a
commit
2eefa3f8e0
83 changed files with 3872 additions and 868 deletions
|
@ -70,6 +70,7 @@ Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol
|
|||
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
|
||||
|
||||
std::string getBuiltinDefinitionSource();
|
||||
std::string getTypeFunctionDefinitionSource();
|
||||
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, TypeId ty, const std::string& packageName);
|
||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);
|
||||
|
|
|
@ -117,12 +117,15 @@ struct ConstraintGenerator
|
|||
|
||||
// Needed to register all available type functions for execution at later stages.
|
||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||
DenseHashMap<const AstStatTypeFunction*, ScopePtr> astTypeFunctionEnvironmentScopes{nullptr};
|
||||
|
||||
// Needed to resolve modules to make 'require' import types properly.
|
||||
NotNull<ModuleResolver> moduleResolver;
|
||||
// Occasionally constraint generation needs to produce an ICE.
|
||||
const NotNull<InternalErrorReporter> ice;
|
||||
|
||||
ScopePtr globalScope;
|
||||
ScopePtr typeFunctionScope;
|
||||
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
||||
std::vector<RequireCycle> requireCycles;
|
||||
|
@ -140,6 +143,7 @@ struct ConstraintGenerator
|
|||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> ice,
|
||||
const ScopePtr& globalScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
DcrLogger* logger,
|
||||
NotNull<DataFlowGraph> dfg,
|
||||
|
|
|
@ -305,6 +305,7 @@ ModulePtr check(
|
|||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
FrontendOptions options,
|
||||
TypeCheckLimits limits
|
||||
|
@ -319,6 +320,7 @@ ModulePtr check(
|
|||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
FrontendOptions options,
|
||||
TypeCheckLimits limits,
|
||||
|
|
|
@ -12,8 +12,9 @@ std::optional<TypeId> generalize(
|
|||
NotNull<TypeArena> arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Scope> scope,
|
||||
NotNull<DenseHashSet<TypeId>> bakedTypes,
|
||||
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||
TypeId ty,
|
||||
/* avoid sealing tables*/ bool avoidSealingTables = false
|
||||
);
|
||||
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ struct GlobalTypes
|
|||
|
||||
TypeArena globalTypes;
|
||||
SourceModule globalNames; // names for symbols entered into globalScope
|
||||
|
||||
ScopePtr globalScope; // shared by all modules
|
||||
ScopePtr globalTypeFunctionScope; // shared by all modules
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -35,7 +35,7 @@ struct Scope
|
|||
explicit Scope(TypePackId returnType); // root scope
|
||||
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
|
||||
|
||||
const ScopePtr parent; // null for the root
|
||||
ScopePtr parent; // null for the root
|
||||
|
||||
// All the children of this scope.
|
||||
std::vector<NotNull<Scope>> children;
|
||||
|
@ -59,6 +59,8 @@ struct Scope
|
|||
|
||||
std::optional<TypeId> lookup(Symbol sym) const;
|
||||
std::optional<TypeId> lookupUnrefinedType(DefId def) const;
|
||||
|
||||
std::optional<TypeId> lookupRValueRefinementType(DefId def) const;
|
||||
std::optional<TypeId> lookup(DefId def) const;
|
||||
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
|
||||
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
|
||||
|
@ -71,6 +73,7 @@ struct Scope
|
|||
|
||||
// WARNING: This function linearly scans for a string key of equal value! It is thus O(n**2)
|
||||
std::optional<Binding> linearSearchForBinding(const std::string& name, bool traverseScopeChain = true) const;
|
||||
std::optional<std::pair<Symbol, Binding>> linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const;
|
||||
|
||||
RefinementMap refinements;
|
||||
|
||||
|
|
|
@ -622,7 +622,6 @@ struct UserDefinedFunctionData
|
|||
AstStatTypeFunction* definition = nullptr;
|
||||
|
||||
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""};
|
||||
DenseHashMap<Name, AstStatTypeFunction*> environment_DEPRECATED{""};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "Luau/TypeOrPack.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -38,18 +40,29 @@ struct Reasonings
|
|||
|
||||
std::string toString()
|
||||
{
|
||||
if (FFlag::LuauImproveTypePathsInErrors && reasons.empty())
|
||||
return "";
|
||||
|
||||
// DenseHashSet ordering is entirely undefined, so we want to
|
||||
// sort the reasons here to achieve a stable error
|
||||
// stringification.
|
||||
std::sort(reasons.begin(), reasons.end());
|
||||
std::string allReasons;
|
||||
std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : "";
|
||||
bool first = true;
|
||||
for (const std::string& reason : reasons)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
if (reasons.size() > 1)
|
||||
allReasons += "\n\t * ";
|
||||
}
|
||||
else
|
||||
allReasons += "\n\t";
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
allReasons += "\n\t";
|
||||
}
|
||||
|
||||
allReasons += reason;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,9 @@ struct TypeFunctionRuntime
|
|||
// Evaluation of type functions should only be performed in the absence of parse errors in the source module
|
||||
bool allowEvaluation = true;
|
||||
|
||||
// Root scope in which the type function operates in, set up by ConstraintGenerator
|
||||
ScopePtr rootScope;
|
||||
|
||||
// Output created by 'print' function
|
||||
std::vector<std::string> messages;
|
||||
|
||||
|
|
|
@ -223,8 +223,6 @@ struct TypeFunctionClassType
|
|||
std::optional<TypeFunctionTypeId> writeParent;
|
||||
|
||||
TypeId classTy;
|
||||
|
||||
std::string name_DEPRECATED;
|
||||
};
|
||||
|
||||
struct TypeFunctionGenericType
|
||||
|
|
|
@ -28,14 +28,8 @@ struct TypeFunctionRuntimeBuilderState
|
|||
{
|
||||
NotNull<TypeFunctionContext> ctx;
|
||||
|
||||
// Mapping of class name to ClassType
|
||||
// Invariant: users can not create a new class types -> any class types that get deserialized must have been an argument to the type function
|
||||
// Using this invariant, whenever a ClassType is serialized, we can put it into this map
|
||||
// whenever a ClassType is deserialized, we can use this map to return the corresponding value
|
||||
DenseHashMap<std::string, TypeId> classesSerialized_DEPRECATED{{}};
|
||||
|
||||
// List of errors that occur during serialization/deserialization
|
||||
// At every iteration of serialization/deserialzation, if this list.size() != 0, we halt the process
|
||||
// At every iteration of serialization/deserialization, if this list.size() != 0, we halt the process
|
||||
std::vector<std::string> errors{};
|
||||
|
||||
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
|
||||
|
|
|
@ -42,9 +42,19 @@ struct Property
|
|||
/// element.
|
||||
struct Index
|
||||
{
|
||||
enum class Variant
|
||||
{
|
||||
Pack,
|
||||
Union,
|
||||
Intersection
|
||||
};
|
||||
|
||||
/// The 0-based index to use for the lookup.
|
||||
size_t index;
|
||||
|
||||
/// The sort of thing we're indexing from, this is used in stringifying the type path for errors.
|
||||
Variant variant;
|
||||
|
||||
bool operator==(const Index& other) const;
|
||||
};
|
||||
|
||||
|
@ -205,6 +215,9 @@ using Path = TypePath::Path;
|
|||
/// terribly clear to end users of the Luau type system.
|
||||
std::string toString(const TypePath::Path& path, bool prefixDot = false);
|
||||
|
||||
/// Converts a Path to a human readable string for error reporting.
|
||||
std::string toStringHuman(const TypePath::Path& path);
|
||||
|
||||
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
||||
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
|
|
|
@ -1065,6 +1065,11 @@ struct AstJsonEncoder : public AstVisitor
|
|||
);
|
||||
}
|
||||
|
||||
void write(class AstTypeOptional* node)
|
||||
{
|
||||
writeNode(node, "AstTypeOptional", [&]() {});
|
||||
}
|
||||
|
||||
void write(class AstTypeUnion* node)
|
||||
{
|
||||
writeNode(
|
||||
|
|
|
@ -30,6 +30,7 @@ LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
|
|||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords =
|
||||
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||
|
@ -484,6 +485,21 @@ static void autocompleteProps(
|
|||
AutocompleteEntryMap inner;
|
||||
std::unordered_set<TypeId> innerSeen;
|
||||
|
||||
// If we don't do this, and we have the misfortune of receiving a
|
||||
// recursive union like:
|
||||
//
|
||||
// t1 where t1 = t1 | Class
|
||||
//
|
||||
// Then we are on a one way journey to a stack overflow.
|
||||
if (FFlag::LuauAutocompleteUnionCopyPreviousSeen)
|
||||
{
|
||||
for (auto ty: seen)
|
||||
{
|
||||
if (is<UnionType, IntersectionType>(ty))
|
||||
innerSeen.insert(ty);
|
||||
}
|
||||
}
|
||||
|
||||
if (isNil(*iter))
|
||||
{
|
||||
++iter;
|
||||
|
|
|
@ -32,8 +32,8 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -288,6 +288,22 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string&
|
|||
}
|
||||
}
|
||||
|
||||
static void finalizeGlobalBindings(ScopePtr scope)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauUserTypeFunTypecheck);
|
||||
|
||||
for (const auto& pair : scope->bindings)
|
||||
{
|
||||
persist(pair.second.typeId);
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
||||
{
|
||||
if (!ttv->name)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
|
||||
{
|
||||
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
|
||||
|
@ -399,14 +415,21 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
// clang-format on
|
||||
}
|
||||
|
||||
for (const auto& pair : globals.globalScope->bindings)
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
persist(pair.second.typeId);
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
||||
finalizeGlobalBindings(globals.globalScope);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& pair : globals.globalScope->bindings)
|
||||
{
|
||||
if (!ttv->name)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
persist(pair.second.typeId);
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
||||
{
|
||||
if (!ttv->name)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -467,6 +490,59 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
TypeId requireTy = getGlobalBinding(globals, "require");
|
||||
attachTag(requireTy, kRequireTagName);
|
||||
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
|
||||
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
// Global scope cannot be the parent of the type checking environment because it can be changed by the embedder
|
||||
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings;
|
||||
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
|
||||
|
||||
// Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined
|
||||
static const char* typeFunctionRuntimeBindings[] = {
|
||||
// Libraries
|
||||
"math",
|
||||
"table",
|
||||
"string",
|
||||
"bit32",
|
||||
"utf8",
|
||||
"buffer",
|
||||
|
||||
// Globals
|
||||
"assert",
|
||||
"error",
|
||||
"print",
|
||||
"next",
|
||||
"ipairs",
|
||||
"pairs",
|
||||
"select",
|
||||
"unpack",
|
||||
"getmetatable",
|
||||
"setmetatable",
|
||||
"rawget",
|
||||
"rawset",
|
||||
"rawlen",
|
||||
"rawequal",
|
||||
"tonumber",
|
||||
"tostring",
|
||||
"type",
|
||||
"typeof",
|
||||
};
|
||||
|
||||
for (auto& name : typeFunctionRuntimeBindings)
|
||||
{
|
||||
AstName astName = globals.globalNames.names->get(name);
|
||||
LUAU_ASSERT(astName.value);
|
||||
|
||||
globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName];
|
||||
}
|
||||
|
||||
LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile(
|
||||
globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false
|
||||
);
|
||||
LUAU_ASSERT(typeFunctionLoadResult.success);
|
||||
|
||||
finalizeGlobalBindings(globals.globalTypeFunctionScope);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
|
||||
|
@ -1444,7 +1520,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context)
|
|||
return false;
|
||||
|
||||
CloneState cloneState{context.solver->builtinTypes};
|
||||
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent);
|
||||
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
|
||||
|
||||
if (auto tableType = getMutable<TableType>(resultType))
|
||||
{
|
||||
|
@ -1481,7 +1557,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, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent);
|
||||
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
|
||||
auto tableTy = getMutable<TableType>(resultType);
|
||||
// `clone` should not break this.
|
||||
LUAU_ASSERT(tableTy);
|
||||
|
|
|
@ -9,13 +9,12 @@
|
|||
#include "Luau/VisitType.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)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
|
||||
|
||||
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -134,7 +133,7 @@ protected:
|
|||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||
if (auto it = types->find(ty); it != types->end())
|
||||
return it->second;
|
||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||
else if (ty->persistent && ty != forceTy)
|
||||
return ty;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -144,7 +143,7 @@ protected:
|
|||
tp = follow(tp);
|
||||
if (auto it = packs->find(tp); it != packs->end())
|
||||
return it->second;
|
||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||
else if (tp->persistent && tp != forceTp)
|
||||
return tp;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -170,7 +169,7 @@ public:
|
|||
|
||||
if (auto clone = find(ty))
|
||||
return *clone;
|
||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||
else if (ty->persistent && ty != forceTy)
|
||||
return ty;
|
||||
|
||||
TypeId target = arena->addType(ty->ty);
|
||||
|
@ -196,7 +195,7 @@ public:
|
|||
|
||||
if (auto clone = find(tp))
|
||||
return *clone;
|
||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||
else if (tp->persistent && tp != forceTp)
|
||||
return tp;
|
||||
|
||||
TypePackId target = arena->addTypePack(tp->ty);
|
||||
|
@ -398,7 +397,7 @@ private:
|
|||
ty = shallowClone(ty);
|
||||
}
|
||||
|
||||
void cloneChildren(LazyType* t)
|
||||
virtual void cloneChildren(LazyType* t)
|
||||
{
|
||||
if (auto unwrapped = t->unwrapped.load())
|
||||
t->unwrapped.store(shallowClone(unwrapped));
|
||||
|
@ -505,7 +504,7 @@ public:
|
|||
|
||||
if (auto clone = find(ty))
|
||||
return *clone;
|
||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
||||
else if (ty->persistent && ty != forceTy)
|
||||
return ty;
|
||||
|
||||
TypeId target = arena->addType(ty->ty);
|
||||
|
@ -539,7 +538,7 @@ public:
|
|||
|
||||
if (auto clone = find(tp))
|
||||
return *clone;
|
||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
||||
else if (tp->persistent && tp != forceTp)
|
||||
return tp;
|
||||
|
||||
TypePackId target = arena->addTypePack(tp->ty);
|
||||
|
@ -553,6 +552,16 @@ public:
|
|||
queue.emplace_back(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
void cloneChildren(LazyType* t) override
|
||||
{
|
||||
// Do not clone lazy types
|
||||
if (!FFlag::LuauIncrementalAutocompleteDemandBasedCloning)
|
||||
{
|
||||
if (auto unwrapped = t->unwrapped.load())
|
||||
t->unwrapped.store(shallowClone(unwrapped));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -560,7 +569,7 @@ public:
|
|||
|
||||
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
|
||||
{
|
||||
if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
|
||||
if (tp->persistent && !ignorePersistent)
|
||||
return tp;
|
||||
|
||||
TypeCloner cloner{
|
||||
|
@ -569,7 +578,7 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
|
|||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
nullptr,
|
||||
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? tp : nullptr
|
||||
ignorePersistent ? tp : nullptr
|
||||
};
|
||||
|
||||
return cloner.shallowClone(tp);
|
||||
|
@ -577,7 +586,7 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
|
|||
|
||||
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
|
||||
{
|
||||
if (typeId->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
|
||||
if (typeId->persistent && !ignorePersistent)
|
||||
return typeId;
|
||||
|
||||
TypeCloner cloner{
|
||||
|
@ -585,7 +594,7 @@ TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool
|
|||
cloneState.builtinTypes,
|
||||
NotNull{&cloneState.seenTypes},
|
||||
NotNull{&cloneState.seenTypePacks},
|
||||
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? typeId : nullptr,
|
||||
ignorePersistent ? typeId : nullptr,
|
||||
nullptr
|
||||
};
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
|||
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -184,6 +185,7 @@ ConstraintGenerator::ConstraintGenerator(
|
|||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> ice,
|
||||
const ScopePtr& globalScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
DcrLogger* logger,
|
||||
NotNull<DataFlowGraph> dfg,
|
||||
|
@ -200,6 +202,7 @@ ConstraintGenerator::ConstraintGenerator(
|
|||
, moduleResolver(moduleResolver)
|
||||
, ice(ice)
|
||||
, globalScope(globalScope)
|
||||
, typeFunctionScope(typeFunctionScope)
|
||||
, prepareModuleScope(std::move(prepareModuleScope))
|
||||
, requireCycles(std::move(requireCycles))
|
||||
, logger(logger)
|
||||
|
@ -221,6 +224,14 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
|||
|
||||
rootScope->returnType = freshTypePack(scope);
|
||||
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
// Create module-local scope for the type function environment
|
||||
ScopePtr localTypeFunctionScope = std::make_shared<Scope>(typeFunctionScope);
|
||||
localTypeFunctionScope->location = block->location;
|
||||
typeFunctionRuntime->rootScope = localTypeFunctionScope;
|
||||
}
|
||||
|
||||
TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, rootScope, builtinTypes->anyTypePack, rootScope->returnType});
|
||||
interiorTypes.emplace_back();
|
||||
|
||||
|
@ -699,6 +710,9 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
|||
std::unordered_map<Name, Location> aliasDefinitionLocations;
|
||||
std::unordered_map<Name, Location> classDefinitionLocations;
|
||||
|
||||
bool hasTypeFunction = false;
|
||||
ScopePtr typeFunctionEnvScope;
|
||||
|
||||
// In order to enable mutually-recursive type aliases, we need to
|
||||
// populate the type bindings before we actually check any of the
|
||||
// alias statements.
|
||||
|
@ -744,6 +758,9 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
|||
}
|
||||
else if (auto function = stat->as<AstStatTypeFunction>())
|
||||
{
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
hasTypeFunction = true;
|
||||
|
||||
// If a type function w/ same name has already been defined, error for having duplicates
|
||||
if (scope->exportedTypeBindings.count(function->name.value) || scope->privateTypeBindings.count(function->name.value))
|
||||
{
|
||||
|
@ -753,7 +770,8 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
|||
continue;
|
||||
}
|
||||
|
||||
ScopePtr defnScope = childScope(function, scope);
|
||||
// Variable becomes unused with the removal of FFlag::LuauUserTypeFunTypecheck
|
||||
ScopePtr defnScope = FFlag::LuauUserTypeFunTypecheck ? nullptr : childScope(function, scope);
|
||||
|
||||
// Create TypeFunctionInstanceType
|
||||
|
||||
|
@ -820,11 +838,22 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauUserTypeFunTypecheck && hasTypeFunction)
|
||||
typeFunctionEnvScope = std::make_shared<Scope>(typeFunctionRuntime->rootScope);
|
||||
|
||||
// Additional pass for user-defined type functions to fill in their environments completely
|
||||
for (AstStat* stat : block->body)
|
||||
{
|
||||
if (auto function = stat->as<AstStatTypeFunction>())
|
||||
{
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
// Similar to global pre-population, create a binding for each type function in the scope upfront
|
||||
TypeId bt = arena->addType(BlockedType{});
|
||||
typeFunctionEnvScope->bindings[function->name] = Binding{bt, function->location};
|
||||
astTypeFunctionEnvironmentScopes[function] = typeFunctionEnvScope;
|
||||
}
|
||||
|
||||
// Find the type function we have already created
|
||||
TypeFunctionInstanceType* mainTypeFun = nullptr;
|
||||
|
||||
|
@ -843,51 +872,60 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
|||
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
|
||||
size_t level = 0;
|
||||
|
||||
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
for (auto& [name, tf] : curr->privateTypeBindings)
|
||||
auto addToEnvironment = [this](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeId type, size_t level)
|
||||
{
|
||||
if (userFuncData.environment.find(name))
|
||||
continue;
|
||||
return;
|
||||
|
||||
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
||||
if (auto ty = get<TypeFunctionInstanceType>(type); ty && ty->userFuncData.definition)
|
||||
{
|
||||
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
|
||||
}
|
||||
|
||||
for (auto& [name, tf] : curr->exportedTypeBindings)
|
||||
if (auto it = astTypeFunctionEnvironmentScopes.find(ty->userFuncData.definition))
|
||||
{
|
||||
if (auto existing = (*it)->linearSearchForBinding(name, /* traverseScopeChain */ false))
|
||||
scope->bindings[ty->userFuncData.definition->name] =
|
||||
Binding{existing->typeId, ty->userFuncData.definition->location};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
|
||||
{
|
||||
if (userFuncData.environment.find(name))
|
||||
continue;
|
||||
for (auto& [name, tf] : curr->privateTypeBindings)
|
||||
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
|
||||
|
||||
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
||||
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
|
||||
for (auto& [name, tf] : curr->exportedTypeBindings)
|
||||
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
|
||||
|
||||
level++;
|
||||
}
|
||||
|
||||
level++;
|
||||
}
|
||||
}
|
||||
else if (mainTypeFun)
|
||||
{
|
||||
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
|
||||
|
||||
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
|
||||
else
|
||||
{
|
||||
for (auto& [name, tf] : curr->privateTypeBindings)
|
||||
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
|
||||
{
|
||||
if (userFuncData.environment_DEPRECATED.find(name))
|
||||
continue;
|
||||
for (auto& [name, tf] : curr->privateTypeBindings)
|
||||
{
|
||||
if (userFuncData.environment.find(name))
|
||||
continue;
|
||||
|
||||
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
||||
userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition;
|
||||
}
|
||||
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
||||
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
|
||||
}
|
||||
|
||||
for (auto& [name, tf] : curr->exportedTypeBindings)
|
||||
{
|
||||
if (userFuncData.environment_DEPRECATED.find(name))
|
||||
continue;
|
||||
for (auto& [name, tf] : curr->exportedTypeBindings)
|
||||
{
|
||||
if (userFuncData.environment.find(name))
|
||||
continue;
|
||||
|
||||
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
||||
userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition;
|
||||
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
||||
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
|
||||
}
|
||||
|
||||
level++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1689,6 +1727,64 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias*
|
|||
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunction* function)
|
||||
{
|
||||
if (!FFlag::LuauUserTypeFunTypecheck)
|
||||
return ControlFlow::None;
|
||||
|
||||
auto scopePtr = astTypeFunctionEnvironmentScopes.find(function);
|
||||
LUAU_ASSERT(scopePtr);
|
||||
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(*scopePtr, function->body, /* expectedType */ std::nullopt);
|
||||
|
||||
// Place this function as a child of the non-type function scope
|
||||
scope->children.push_back(NotNull{sig.signatureScope.get()});
|
||||
|
||||
interiorTypes.push_back(std::vector<TypeId>{});
|
||||
checkFunctionBody(sig.bodyScope, function->body);
|
||||
Checkpoint endCheckpoint = checkpoint(this);
|
||||
|
||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||
NotNull<Constraint> gc = addConstraint(
|
||||
sig.signatureScope,
|
||||
function->location,
|
||||
GeneralizationConstraint{
|
||||
generalizedTy, sig.signature, FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector<TypeId>{} : std::move(interiorTypes.back())
|
||||
}
|
||||
);
|
||||
|
||||
if (FFlag::LuauTrackInteriorFreeTypesOnScope)
|
||||
sig.signatureScope->interiorFreeTypes = std::move(interiorTypes.back());
|
||||
|
||||
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
|
||||
interiorTypes.pop_back();
|
||||
|
||||
Constraint* previous = nullptr;
|
||||
forEachConstraint(
|
||||
startCheckpoint,
|
||||
endCheckpoint,
|
||||
this,
|
||||
[gc, &previous](const ConstraintPtr& constraint)
|
||||
{
|
||||
gc->dependencies.emplace_back(constraint.get());
|
||||
|
||||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
{
|
||||
if (previous)
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
|
||||
previous = constraint.get();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
std::optional<TypeId> existingFunctionTy = (*scopePtr)->lookup(function->name);
|
||||
|
||||
if (!existingFunctionTy)
|
||||
ice->ice("checkAliases did not populate type function name", function->nameLocation);
|
||||
|
||||
if (auto bt = get<BlockedType>(*existingFunctionTy); bt && nullptr == bt->getOwner())
|
||||
emplaceType<BoundType>(asMutable(*existingFunctionTy), generalizedTy);
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
|
@ -3094,7 +3190,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
|
|||
signatureScope = childScope(fn, parent);
|
||||
|
||||
// We need to assign returnType before creating bodyScope so that the
|
||||
// return type gets propogated to bodyScope.
|
||||
// return type gets propagated to bodyScope.
|
||||
returnType = freshTypePack(signatureScope);
|
||||
signatureScope->returnType = returnType;
|
||||
|
||||
|
@ -3520,6 +3616,10 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
|||
TypeId exprType = check(scope, tof->expr).ty;
|
||||
result = exprType;
|
||||
}
|
||||
else if (ty->is<AstTypeOptional>())
|
||||
{
|
||||
return builtinTypes->nilType;
|
||||
}
|
||||
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
|
||||
{
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
|
@ -3882,9 +3982,18 @@ struct GlobalPrepopulator : AstVisitor
|
|||
void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program)
|
||||
{
|
||||
FragmentTypeCheckGlobalPrepopulator gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena};
|
||||
|
||||
if (prepareModuleScope)
|
||||
prepareModuleScope(module->name, resumeScope);
|
||||
|
||||
program->visit(&gp);
|
||||
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
// Handle type function globals as well, without preparing a module scope since they have a separate environment
|
||||
GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
|
||||
program->visit(&tfgp);
|
||||
}
|
||||
}
|
||||
|
||||
void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
|
||||
|
@ -3895,6 +4004,13 @@ void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, As
|
|||
prepareModuleScope(module->name, globalScope);
|
||||
|
||||
program->visit(&gp);
|
||||
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
// Handle type function globals as well, without preparing a module scope since they have a separate environment
|
||||
GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
|
||||
program->visit(&tfgp);
|
||||
}
|
||||
}
|
||||
|
||||
bool ConstraintGenerator::recordPropertyAssignment(TypeId ty)
|
||||
|
|
|
@ -1168,6 +1168,8 @@ void DataFlowGraphBuilder::visitType(AstType* t)
|
|||
return visitType(f);
|
||||
else if (auto tyof = t->as<AstTypeTypeof>())
|
||||
return visitType(tyof);
|
||||
else if (auto o = t->as<AstTypeOptional>())
|
||||
return;
|
||||
else if (auto u = t->as<AstTypeUnion>())
|
||||
return visitType(u);
|
||||
else if (auto i = t->as<AstTypeIntersection>())
|
||||
|
|
|
@ -317,4 +317,83 @@ std::string getBuiltinDefinitionSource()
|
|||
return result;
|
||||
}
|
||||
|
||||
// TODO: split into separate tagged unions when the new solver can appropriately handle that.
|
||||
static const std::string kBuiltinDefinitionTypesSrc = R"BUILTIN_SRC(
|
||||
|
||||
export type type = {
|
||||
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
|
||||
"singleton" | "negation" | "union" | "intesection" | "table" | "function" | "class" | "generic",
|
||||
|
||||
is: (self: type, arg: string) -> boolean,
|
||||
|
||||
-- for singleton type
|
||||
value: (self: type) -> (string | boolean | nil),
|
||||
|
||||
-- for negation type
|
||||
inner: (self: type) -> type,
|
||||
|
||||
-- for union and intersection types
|
||||
components: (self: type) -> {type},
|
||||
|
||||
-- for table type
|
||||
setproperty: (self: type, key: type, value: type?) -> (),
|
||||
setreadproperty: (self: type, key: type, value: type?) -> (),
|
||||
setwriteproperty: (self: type, key: type, value: type?) -> (),
|
||||
readproperty: (self: type, key: type) -> type?,
|
||||
writeproperty: (self: type, key: type) -> type?,
|
||||
properties: (self: type) -> { [type]: { read: type?, write: type? } },
|
||||
setindexer: (self: type, index: type, result: type) -> (),
|
||||
setreadindexer: (self: type, index: type, result: type) -> (),
|
||||
setwriteindexer: (self: type, index: type, result: type) -> (),
|
||||
indexer: (self: type) -> { index: type, readresult: type, writeresult: type }?,
|
||||
readindexer: (self: type) -> { index: type, result: type }?,
|
||||
writeindexer: (self: type) -> { index: type, result: type }?,
|
||||
setmetatable: (self: type, arg: type) -> (),
|
||||
metatable: (self: type) -> type?,
|
||||
|
||||
-- for function type
|
||||
setparameters: (self: type, head: {type}?, tail: type?) -> (),
|
||||
parameters: (self: type) -> { head: {type}?, tail: type? },
|
||||
setreturns: (self: type, head: {type}?, tail: type? ) -> (),
|
||||
returns: (self: type) -> { head: {type}?, tail: type? },
|
||||
setgenerics: (self: type, {type}?) -> (),
|
||||
generics: (self: type) -> {type},
|
||||
|
||||
-- for class type
|
||||
-- 'properties', 'metatable', 'indexer', 'readindexer' and 'writeindexer' are shared with table type
|
||||
readparent: (self: type) -> type?,
|
||||
writeparent: (self: type) -> type?,
|
||||
|
||||
-- for generic type
|
||||
name: (self: type) -> string?,
|
||||
ispack: (self: type) -> boolean,
|
||||
}
|
||||
|
||||
declare types: {
|
||||
unknown: type,
|
||||
never: type,
|
||||
any: type,
|
||||
boolean: type,
|
||||
number: type,
|
||||
string: type,
|
||||
thread: type,
|
||||
buffer: type,
|
||||
|
||||
singleton: @checked (arg: string | boolean | nil) -> type,
|
||||
generic: @checked (name: string, ispack: boolean?) -> type,
|
||||
negationof: @checked (arg: type) -> type,
|
||||
unionof: @checked (...type) -> type,
|
||||
intersectionof: @checked (...type) -> type,
|
||||
newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type,
|
||||
newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type,
|
||||
copy: @checked (arg: type) -> type,
|
||||
}
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
std::string getTypeFunctionDefinitionSource()
|
||||
{
|
||||
return kBuiltinDefinitionTypesSrc;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/TypeFunction.h"
|
||||
|
||||
#include <optional>
|
||||
|
@ -17,6 +18,7 @@
|
|||
#include <unordered_set>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
||||
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix)
|
||||
|
||||
static std::string wrongNumberOfArgsString(
|
||||
size_t expectedCount,
|
||||
|
@ -116,7 +118,10 @@ struct ErrorConverter
|
|||
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
|
||||
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
|
||||
return "Type " + given + " could not be converted into " + wanted;
|
||||
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
return "Type\n\t" + given + "\ncould not be converted into\n\t" + wanted;
|
||||
else
|
||||
return "Type\n " + given + "\ncould not be converted into\n " + wanted;
|
||||
};
|
||||
|
||||
if (givenTypeName == wantedTypeName)
|
||||
|
@ -751,8 +756,15 @@ struct ErrorConverter
|
|||
|
||||
std::string operator()(const NonStrictFunctionDefinitionError& e) const
|
||||
{
|
||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
||||
"' is used in a way that will run time error";
|
||||
if (FFlag::LuauNonStrictFuncDefErrorFix && e.functionName.empty())
|
||||
{
|
||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
||||
"' is used in a way that will run time error";
|
||||
}
|
||||
}
|
||||
|
||||
std::string operator()(const PropertyAccessViolation& e) const
|
||||
|
|
|
@ -39,6 +39,9 @@ LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
|
|||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -84,8 +87,140 @@ void cloneModuleMap(
|
|||
}
|
||||
}
|
||||
|
||||
struct UsageFinder : public AstVisitor
|
||||
{
|
||||
|
||||
explicit UsageFinder(NotNull<DataFlowGraph> dfg)
|
||||
: dfg(dfg)
|
||||
{
|
||||
// We explicitly suggest that the usage finder propulate types for instance and enum by default
|
||||
// These are common enough types that sticking them in the environment is a good idea
|
||||
// and it lets magic functions work correctly too.
|
||||
referencedBindings.emplace_back("Instance");
|
||||
referencedBindings.emplace_back("Enum");
|
||||
}
|
||||
|
||||
bool visit(AstExprConstantString* expr) override
|
||||
{
|
||||
// Populating strings in the referenced bindings is nice too, because it means that magic functions that look
|
||||
// up types by names will work correctly too.
|
||||
// Only if the actual type alias exists will we populate it over, otherwise, the strings will just get ignored
|
||||
referencedBindings.emplace_back(expr->value.data, expr->value.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstType* node) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatTypeAlias* alias) override
|
||||
{
|
||||
declaredAliases.insert(std::string(alias->name.value));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstTypeReference* ref) override
|
||||
{
|
||||
if (std::optional<AstName> prefix = ref->prefix)
|
||||
referencedImportedBindings.emplace_back(prefix->value, ref->name.value);
|
||||
else
|
||||
referencedBindings.emplace_back(ref->name.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstExpr* expr) override
|
||||
{
|
||||
if (auto opt = dfg->getDefOptional(expr))
|
||||
mentionedDefs.insert(opt->get());
|
||||
if (auto ref = dfg->getRefinementKey(expr))
|
||||
mentionedDefs.insert(ref->def);
|
||||
if (auto local = expr->as<AstExprLocal>())
|
||||
localBindingsReferenced.emplace_back(dfg->getDef(local), local->local);
|
||||
return true;
|
||||
}
|
||||
|
||||
NotNull<DataFlowGraph> dfg;
|
||||
DenseHashSet<Name> declaredAliases{""};
|
||||
std::vector<std::pair<const Def*, AstLocal*>> localBindingsReferenced;
|
||||
DenseHashSet<const Def*> mentionedDefs{nullptr};
|
||||
std::vector<Name> referencedBindings{""};
|
||||
std::vector<std::pair<Name, Name>> referencedImportedBindings{{"", ""}};
|
||||
};
|
||||
|
||||
// Runs the `UsageFinder` traversal on the fragment and grabs all of the types that are
|
||||
// referenced in the fragment. We'll clone these and place them in the appropriate spots
|
||||
// in the scope so that they are available during typechecking.
|
||||
void cloneTypesFromFragment(
|
||||
CloneState& cloneState,
|
||||
const Scope* staleScope,
|
||||
const ModulePtr& staleModule,
|
||||
NotNull<TypeArena> destArena,
|
||||
NotNull<DataFlowGraph> dfg,
|
||||
AstStatBlock* program,
|
||||
Scope* destScope
|
||||
)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Luau::cloneTypesFromFragment", "FragmentAutocomplete");
|
||||
|
||||
UsageFinder f{dfg};
|
||||
program->visit(&f);
|
||||
// These are defs that have been mentioned. find the appropriate lvalue type and rvalue types and place them in the scope
|
||||
// First - any locals that have been mentioned in the fragment need to be placed in the bindings and lvalueTypes secionts.
|
||||
|
||||
for (const auto& d : f.mentionedDefs)
|
||||
{
|
||||
if (std::optional<TypeId> rValueRefinement = staleScope->lookupRValueRefinementType(NotNull{d}))
|
||||
{
|
||||
destScope->rvalueRefinements[d] = Luau::cloneIncremental(*rValueRefinement, *destArena, cloneState, destScope);
|
||||
}
|
||||
|
||||
if (std::optional<TypeId> lValue = staleScope->lookupUnrefinedType(NotNull{d}))
|
||||
{
|
||||
destScope->lvalueTypes[d] = Luau::cloneIncremental(*lValue, *destArena, cloneState, destScope);
|
||||
}
|
||||
}
|
||||
for (const auto& [d, loc] : f.localBindingsReferenced)
|
||||
{
|
||||
if (std::optional<std::pair<Symbol, Binding>> pair = staleScope->linearSearchForBindingPair(loc->name.value, true))
|
||||
{
|
||||
destScope->lvalueTypes[d] = Luau::cloneIncremental(pair->second.typeId, *destArena, cloneState, destScope);
|
||||
destScope->bindings[pair->first] = Luau::cloneIncremental(pair->second, *destArena, cloneState, destScope);
|
||||
}
|
||||
}
|
||||
|
||||
// Second - any referenced type alias bindings need to be placed in scope so type annotation can be resolved.
|
||||
// If the actual type alias appears in the fragment on the lhs as a definition (in declaredAliases), it will be processed during typechecking
|
||||
// anyway
|
||||
for (const auto& x : f.referencedBindings)
|
||||
{
|
||||
if (f.declaredAliases.contains(x))
|
||||
continue;
|
||||
if (std::optional<TypeFun> tf = staleScope->lookupType(x))
|
||||
{
|
||||
destScope->privateTypeBindings[x] = Luau::cloneIncremental(*tf, *destArena, cloneState, destScope);
|
||||
}
|
||||
}
|
||||
|
||||
// Third - any referenced imported type bindings need to be imported in
|
||||
for (const auto& [mod, name] : f.referencedImportedBindings)
|
||||
{
|
||||
if (std::optional<TypeFun> tf = staleScope->lookupImportedType(mod, name))
|
||||
{
|
||||
destScope->importedTypeBindings[mod].insert_or_assign(name, Luau::cloneIncremental(*tf, *destArena, cloneState, destScope));
|
||||
}
|
||||
}
|
||||
|
||||
// Finally - clone the returnType on the staleScope. This helps avoid potential leaks of free types.
|
||||
if (staleScope->returnType)
|
||||
destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope);
|
||||
}
|
||||
|
||||
|
||||
struct MixedModeIncrementalTCDefFinder : public AstVisitor
|
||||
{
|
||||
|
||||
bool visit(AstExprLocal* local) override
|
||||
{
|
||||
referencedLocalDefs.emplace_back(local->local, local);
|
||||
|
@ -337,6 +472,17 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* ro
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (auto typeFun = stat->as<AstStatTypeFunction>(); typeFun && FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
if (typeFun->location.contains(cursorPos))
|
||||
{
|
||||
for (AstLocal* loc : typeFun->body->args)
|
||||
{
|
||||
localStack.push_back(loc);
|
||||
localMap[loc->name] = loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -622,7 +768,7 @@ static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::s
|
|||
reporter->reportFragmentString(fragment);
|
||||
}
|
||||
|
||||
FragmentTypeCheckResult typecheckFragment_(
|
||||
FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
|
||||
Frontend& frontend,
|
||||
AstStatBlock* root,
|
||||
const ModulePtr& stale,
|
||||
|
@ -692,6 +838,7 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
frontend.builtinTypes,
|
||||
iceHandler,
|
||||
stale->getModuleScope(),
|
||||
frontend.globals.globalTypeFunctionScope,
|
||||
nullptr,
|
||||
nullptr,
|
||||
NotNull{&dfg},
|
||||
|
@ -729,7 +876,7 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
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
|
||||
// We need to trim nearestChild from the scope hierarchy
|
||||
closestScope->children.emplace_back(freshChildOfNearestScope.get());
|
||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||
// Trim nearestChild from the closestScope
|
||||
|
@ -788,6 +935,149 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
return {std::move(incrementalModule), std::move(freshChildOfNearestScope)};
|
||||
}
|
||||
|
||||
FragmentTypeCheckResult typecheckFragment_(
|
||||
Frontend& frontend,
|
||||
AstStatBlock* root,
|
||||
const ModulePtr& stale,
|
||||
const ScopePtr& closestScope,
|
||||
const Position& cursorPos,
|
||||
std::unique_ptr<Allocator> astAllocator,
|
||||
const FrontendOptions& opts,
|
||||
IFragmentAutocompleteReporter* reporter
|
||||
)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete");
|
||||
|
||||
freeze(stale->internalTypes);
|
||||
freeze(stale->interfaceTypes);
|
||||
ModulePtr incrementalModule = std::make_shared<Module>();
|
||||
incrementalModule->name = stale->name;
|
||||
incrementalModule->humanReadableName = "Incremental$" + stale->humanReadableName;
|
||||
incrementalModule->internalTypes.owningModule = incrementalModule.get();
|
||||
incrementalModule->interfaceTypes.owningModule = incrementalModule.get();
|
||||
incrementalModule->allocator = std::move(astAllocator);
|
||||
incrementalModule->checkedInNewSolver = true;
|
||||
unfreeze(incrementalModule->internalTypes);
|
||||
unfreeze(incrementalModule->interfaceTypes);
|
||||
|
||||
/// Setup typecheck limits
|
||||
TypeCheckLimits limits;
|
||||
if (opts.moduleTimeLimitSec)
|
||||
limits.finishTime = TimeTrace::getClock() + *opts.moduleTimeLimitSec;
|
||||
else
|
||||
limits.finishTime = std::nullopt;
|
||||
limits.cancellationToken = opts.cancellationToken;
|
||||
|
||||
/// Icehandler
|
||||
NotNull<InternalErrorReporter> iceHandler{&frontend.iceHandler};
|
||||
/// Make the shared state for the unifier (recursion + iteration limits)
|
||||
UnifierSharedState unifierState{iceHandler};
|
||||
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
|
||||
|
||||
/// Initialize the normalizer
|
||||
Normalizer normalizer{&incrementalModule->internalTypes, frontend.builtinTypes, NotNull{&unifierState}};
|
||||
|
||||
/// User defined type functions runtime
|
||||
TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits});
|
||||
|
||||
if (FFlag::LuauFragmentNoTypeFunEval || FFlag::LuauUserTypeFunTypecheck)
|
||||
typeFunctionRuntime.allowEvaluation = false;
|
||||
|
||||
/// Create a DataFlowGraph just for the surrounding context
|
||||
DataFlowGraph dfg = DataFlowGraphBuilder::build(root, NotNull{&incrementalModule->defArena}, NotNull{&incrementalModule->keyArena}, iceHandler);
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::DfgBuildEnd);
|
||||
|
||||
SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes);
|
||||
|
||||
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
|
||||
|
||||
/// Contraint Generator
|
||||
ConstraintGenerator cg{
|
||||
incrementalModule,
|
||||
NotNull{&normalizer},
|
||||
NotNull{simplifier.get()},
|
||||
NotNull{&typeFunctionRuntime},
|
||||
NotNull{&resolver},
|
||||
frontend.builtinTypes,
|
||||
iceHandler,
|
||||
stale->getModuleScope(),
|
||||
frontend.globals.globalTypeFunctionScope,
|
||||
nullptr,
|
||||
nullptr,
|
||||
NotNull{&dfg},
|
||||
{}
|
||||
};
|
||||
|
||||
CloneState cloneState{frontend.builtinTypes};
|
||||
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
|
||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||
freshChildOfNearestScope->interiorFreeTypes.emplace();
|
||||
cg.rootScope = freshChildOfNearestScope.get();
|
||||
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
// Create module-local scope for the type function environment
|
||||
ScopePtr localTypeFunctionScope = std::make_shared<Scope>(cg.typeFunctionScope);
|
||||
localTypeFunctionScope->location = root->location;
|
||||
cg.typeFunctionRuntime->rootScope = localTypeFunctionScope;
|
||||
}
|
||||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
|
||||
cloneTypesFromFragment(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
);
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
|
||||
|
||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||
|
||||
for (auto p : cg.scopes)
|
||||
incrementalModule->scopes.emplace_back(std::move(p));
|
||||
|
||||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart);
|
||||
|
||||
/// Initialize the constraint solver and run it
|
||||
ConstraintSolver cs{
|
||||
NotNull{&normalizer},
|
||||
NotNull{simplifier.get()},
|
||||
NotNull{&typeFunctionRuntime},
|
||||
NotNull(cg.rootScope),
|
||||
borrowConstraints(cg.constraints),
|
||||
NotNull{&cg.scopeToFunction},
|
||||
incrementalModule->name,
|
||||
NotNull{&resolver},
|
||||
{},
|
||||
nullptr,
|
||||
NotNull{&dfg},
|
||||
limits
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
cs.run();
|
||||
}
|
||||
catch (const TimeLimitError&)
|
||||
{
|
||||
stale->timeout = true;
|
||||
}
|
||||
catch (const UserCancelError&)
|
||||
{
|
||||
stale->cancelled = true;
|
||||
}
|
||||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverEnd);
|
||||
|
||||
// In frontend we would forbid internal types
|
||||
// because this is just for autocomplete, we don't actually care
|
||||
// We also don't even need to typecheck - just synthesize types as best as we can
|
||||
|
||||
freeze(incrementalModule->internalTypes);
|
||||
freeze(incrementalModule->interfaceTypes);
|
||||
freshChildOfNearestScope->parent = closestScope;
|
||||
return {std::move(incrementalModule), std::move(freshChildOfNearestScope)};
|
||||
}
|
||||
|
||||
|
||||
std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
||||
Frontend& frontend,
|
||||
|
@ -850,7 +1140,11 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
FrontendOptions frontendOptions = opts.value_or(frontend.options);
|
||||
const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement);
|
||||
FragmentTypeCheckResult result =
|
||||
typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter);
|
||||
FFlag::LuauIncrementalAutocompleteDemandBasedCloning
|
||||
? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter)
|
||||
: typecheckFragmentHelper_DEPRECATED(
|
||||
frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter
|
||||
);
|
||||
result.ancestry = std::move(parseResult.ancestry);
|
||||
reportFragmentString(reporter, tryParse->fragmentToParse);
|
||||
return {FragmentTypeCheckStatus::Success, result};
|
||||
|
|
|
@ -1532,6 +1532,7 @@ ModulePtr check(
|
|||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& parentScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
FrontendOptions options,
|
||||
TypeCheckLimits limits,
|
||||
|
@ -1548,6 +1549,7 @@ ModulePtr check(
|
|||
moduleResolver,
|
||||
fileResolver,
|
||||
parentScope,
|
||||
typeFunctionScope,
|
||||
std::move(prepareModuleScope),
|
||||
options,
|
||||
limits,
|
||||
|
@ -1609,6 +1611,7 @@ ModulePtr check(
|
|||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& parentScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
FrontendOptions options,
|
||||
TypeCheckLimits limits,
|
||||
|
@ -1666,6 +1669,7 @@ ModulePtr check(
|
|||
builtinTypes,
|
||||
iceHandler,
|
||||
parentScope,
|
||||
typeFunctionScope,
|
||||
std::move(prepareModuleScope),
|
||||
logger.get(),
|
||||
NotNull{&dfg},
|
||||
|
@ -1848,6 +1852,7 @@ ModulePtr Frontend::check(
|
|||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
|
||||
NotNull{fileResolver},
|
||||
environmentScope ? *environmentScope : globals.globalScope,
|
||||
globals.globalTypeFunctionScope,
|
||||
prepareModuleScopeWrap,
|
||||
options,
|
||||
typeCheckLimits,
|
||||
|
|
|
@ -359,7 +359,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
DenseHashSet<const void*> seenPositive{nullptr};
|
||||
DenseHashSet<const void*> seenNegative{nullptr};
|
||||
|
||||
bool seenWithPolarity(const void* ty)
|
||||
bool seenWithCurrentPolarity(const void* ty)
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
|
@ -401,7 +401,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
||||
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
LUAU_ASSERT(ty);
|
||||
|
@ -410,7 +410,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
{
|
||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
||||
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
if (!subsumes(scope, ft.scope))
|
||||
|
@ -435,7 +435,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const TableType& tt) override
|
||||
{
|
||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
||||
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
|
||||
|
@ -481,7 +481,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const FunctionType& ft) override
|
||||
{
|
||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
||||
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
flip();
|
||||
|
@ -500,7 +500,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||
{
|
||||
if (seenWithPolarity(tp))
|
||||
if (seenWithCurrentPolarity(tp))
|
||||
return false;
|
||||
|
||||
if (!subsumes(scope, ftp.scope))
|
||||
|
@ -547,7 +547,7 @@ struct TypeCacher : TypeOnceVisitor
|
|||
{
|
||||
}
|
||||
|
||||
void cache(TypeId ty)
|
||||
void cache(TypeId ty) const
|
||||
{
|
||||
cachedTypes->insert(ty);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
|
|||
: builtinTypes(builtinTypes)
|
||||
{
|
||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||
globalTypeFunctionScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
||||
|
||||
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType});
|
||||
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNonStrictFuncDefErrorFix)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -763,7 +764,17 @@ struct NonStrictTypeChecker
|
|||
for (AstLocal* local : exprFn->args)
|
||||
{
|
||||
if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder))
|
||||
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
|
||||
{
|
||||
if (FFlag::LuauNonStrictFuncDefErrorFix)
|
||||
{
|
||||
const char* debugname = exprFn->debugname.value;
|
||||
reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location);
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location);
|
||||
}
|
||||
}
|
||||
remainder.remove(dfg->getDef(local));
|
||||
}
|
||||
return remainder;
|
||||
|
|
|
@ -22,7 +22,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegationFix)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixNormalizedIntersectionOfNegatedClass)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -304,7 +304,9 @@ bool NormalizedType::isUnknown() const
|
|||
|
||||
// Otherwise, we can still be unknown!
|
||||
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
|
||||
strings.isString() && isPrim(threads, PrimitiveType::Thread) && isThread(threads);
|
||||
strings.isString() &&
|
||||
(FFlag::LuauNormalizedBufferIsNotUnknown ? isThread(threads) && isBuffer(buffers)
|
||||
: isPrim(threads, PrimitiveType::Thread) && isThread(threads));
|
||||
|
||||
// Check is class
|
||||
bool isTopClass = false;
|
||||
|
@ -2289,7 +2291,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
|||
|
||||
for (auto nIt = negations.begin(); nIt != negations.end();)
|
||||
{
|
||||
if (FFlag::LuauFixNormalizedIntersectionOfNegatedClass && isSubclass(there, *nIt))
|
||||
if (isSubclass(there, *nIt))
|
||||
{
|
||||
// Hitting this block means that the incoming class is a
|
||||
// subclass of this type, _and_ one of its negations is a
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
#include "Luau/Ast.h"
|
||||
#include "Luau/Module.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendedSimpleRequire)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -106,96 +104,50 @@ struct RequireTracer : AstVisitor
|
|||
{
|
||||
ModuleInfo moduleContext{currentModuleName};
|
||||
|
||||
if (FFlag::LuauExtendedSimpleRequire)
|
||||
// seed worklist with require arguments
|
||||
work.reserve(requireCalls.size());
|
||||
|
||||
for (AstExprCall* require : requireCalls)
|
||||
work.push_back(require->args.data[0]);
|
||||
|
||||
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
||||
for (size_t i = 0; i < work.size(); ++i)
|
||||
{
|
||||
// seed worklist with require arguments
|
||||
work.reserve(requireCalls.size());
|
||||
|
||||
for (AstExprCall* require : requireCalls)
|
||||
work.push_back(require->args.data[0]);
|
||||
|
||||
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
||||
for (size_t i = 0; i < work.size(); ++i)
|
||||
{
|
||||
if (AstNode* dep = getDependent(work[i]))
|
||||
work.push_back(dep);
|
||||
}
|
||||
|
||||
// resolve all expressions to a module info
|
||||
for (size_t i = work.size(); i > 0; --i)
|
||||
{
|
||||
AstNode* expr = work[i - 1];
|
||||
|
||||
// when multiple expressions depend on the same one we push it to work queue multiple times
|
||||
if (result.exprs.contains(expr))
|
||||
continue;
|
||||
|
||||
std::optional<ModuleInfo> info;
|
||||
|
||||
if (AstNode* dep = getDependent(expr))
|
||||
{
|
||||
const ModuleInfo* context = result.exprs.find(dep);
|
||||
|
||||
if (context && expr->is<AstExprLocal>())
|
||||
info = *context; // locals just inherit their dependent context, no resolution required
|
||||
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
|
||||
info = *context; // simple group nodes propagate their value
|
||||
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
|
||||
info = *context; // typeof type annotations will resolve to the typeof content
|
||||
else if (AstExpr* asExpr = expr->asExpr())
|
||||
info = fileResolver->resolveModule(context, asExpr);
|
||||
}
|
||||
else if (AstExpr* asExpr = expr->asExpr())
|
||||
{
|
||||
info = fileResolver->resolveModule(&moduleContext, asExpr);
|
||||
}
|
||||
|
||||
if (info)
|
||||
result.exprs[expr] = std::move(*info);
|
||||
}
|
||||
if (AstNode* dep = getDependent(work[i]))
|
||||
work.push_back(dep);
|
||||
}
|
||||
else
|
||||
|
||||
// resolve all expressions to a module info
|
||||
for (size_t i = work.size(); i > 0; --i)
|
||||
{
|
||||
// seed worklist with require arguments
|
||||
work_DEPRECATED.reserve(requireCalls.size());
|
||||
AstNode* expr = work[i - 1];
|
||||
|
||||
for (AstExprCall* require : requireCalls)
|
||||
work_DEPRECATED.push_back(require->args.data[0]);
|
||||
// when multiple expressions depend on the same one we push it to work queue multiple times
|
||||
if (result.exprs.contains(expr))
|
||||
continue;
|
||||
|
||||
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
||||
for (size_t i = 0; i < work_DEPRECATED.size(); ++i)
|
||||
if (AstExpr* dep = getDependent_DEPRECATED(work_DEPRECATED[i]))
|
||||
work_DEPRECATED.push_back(dep);
|
||||
std::optional<ModuleInfo> info;
|
||||
|
||||
// resolve all expressions to a module info
|
||||
for (size_t i = work_DEPRECATED.size(); i > 0; --i)
|
||||
if (AstNode* dep = getDependent(expr))
|
||||
{
|
||||
AstExpr* expr = work_DEPRECATED[i - 1];
|
||||
const ModuleInfo* context = result.exprs.find(dep);
|
||||
|
||||
// when multiple expressions depend on the same one we push it to work queue multiple times
|
||||
if (result.exprs.contains(expr))
|
||||
continue;
|
||||
|
||||
std::optional<ModuleInfo> info;
|
||||
|
||||
if (AstExpr* dep = getDependent_DEPRECATED(expr))
|
||||
{
|
||||
const ModuleInfo* context = result.exprs.find(dep);
|
||||
|
||||
// locals just inherit their dependent context, no resolution required
|
||||
if (expr->is<AstExprLocal>())
|
||||
info = context ? std::optional<ModuleInfo>(*context) : std::nullopt;
|
||||
else
|
||||
info = fileResolver->resolveModule(context, expr);
|
||||
}
|
||||
else
|
||||
{
|
||||
info = fileResolver->resolveModule(&moduleContext, expr);
|
||||
}
|
||||
|
||||
if (info)
|
||||
result.exprs[expr] = std::move(*info);
|
||||
if (context && expr->is<AstExprLocal>())
|
||||
info = *context; // locals just inherit their dependent context, no resolution required
|
||||
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
|
||||
info = *context; // simple group nodes propagate their value
|
||||
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
|
||||
info = *context; // typeof type annotations will resolve to the typeof content
|
||||
else if (AstExpr* asExpr = expr->asExpr())
|
||||
info = fileResolver->resolveModule(context, asExpr);
|
||||
}
|
||||
else if (AstExpr* asExpr = expr->asExpr())
|
||||
{
|
||||
info = fileResolver->resolveModule(&moduleContext, asExpr);
|
||||
}
|
||||
|
||||
if (info)
|
||||
result.exprs[expr] = std::move(*info);
|
||||
}
|
||||
|
||||
// resolve all requires according to their argument
|
||||
|
@ -224,7 +176,6 @@ struct RequireTracer : AstVisitor
|
|||
ModuleName currentModuleName;
|
||||
|
||||
DenseHashMap<AstLocal*, AstExpr*> locals;
|
||||
std::vector<AstExpr*> work_DEPRECATED;
|
||||
std::vector<AstNode*> work;
|
||||
std::vector<AstExprCall*> requireCalls;
|
||||
};
|
||||
|
|
|
@ -84,6 +84,17 @@ std::optional<TypeId> Scope::lookupUnrefinedType(DefId def) const
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> Scope::lookupRValueRefinementType(DefId def) const
|
||||
{
|
||||
for (const Scope* current = this; current; current = current->parent.get())
|
||||
{
|
||||
if (auto ty = current->rvalueRefinements.find(def))
|
||||
return *ty;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> Scope::lookup(DefId def) const
|
||||
{
|
||||
for (const Scope* current = this; current; current = current->parent.get())
|
||||
|
@ -181,6 +192,29 @@ std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bo
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::pair<Symbol, Binding>> Scope::linearSearchForBindingPair(const std::string& name, bool traverseScopeChain) const
|
||||
{
|
||||
const Scope* scope = this;
|
||||
|
||||
while (scope)
|
||||
{
|
||||
for (auto& [n, binding] : scope->bindings)
|
||||
{
|
||||
if (n.local && n.local->name == name.c_str())
|
||||
return {{n, binding}};
|
||||
else if (n.global.value && n.global == name.c_str())
|
||||
return {{n, binding}};
|
||||
}
|
||||
|
||||
scope = scope->parent.get();
|
||||
|
||||
if (!traverseScopeChain)
|
||||
break;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
|
||||
void Scope::inheritAssignments(const ScopePtr& childScope)
|
||||
{
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingFixTailPack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -754,7 +753,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
// Match head types pairwise
|
||||
|
||||
for (size_t i = 0; i < headSize; ++i)
|
||||
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i}));
|
||||
results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
|
||||
);
|
||||
|
||||
// Handle mismatched head sizes
|
||||
|
||||
|
@ -767,7 +767,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
for (size_t i = headSize; i < superHead.size(); ++i)
|
||||
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
|
||||
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
|
||||
.withSuperComponent(TypePath::Index{i}));
|
||||
.withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}));
|
||||
}
|
||||
else if (auto gt = get<GenericTypePack>(*subTail))
|
||||
{
|
||||
|
@ -821,7 +821,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
{
|
||||
for (size_t i = headSize; i < subHead.size(); ++i)
|
||||
results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope)
|
||||
.withSubComponent(TypePath::Index{i})
|
||||
.withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
|
||||
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
|
||||
}
|
||||
else if (auto gt = get<GenericTypePack>(*superTail))
|
||||
|
@ -859,7 +859,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
else
|
||||
return SubtypingResult{false}
|
||||
.withSuperComponent(TypePath::PackField::Tail)
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{FFlag::LuauSubtypingFixTailPack ? *superTail : *subTail}});
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
|
||||
}
|
||||
else
|
||||
return {false};
|
||||
|
@ -1100,7 +1100,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
|
|||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
for (TypeId ty : subUnion)
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
|
||||
return SubtypingResult::all(subtypings);
|
||||
}
|
||||
|
||||
|
@ -1110,7 +1110,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
for (TypeId ty : superIntersection)
|
||||
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++}));
|
||||
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
|
||||
return SubtypingResult::all(subtypings);
|
||||
}
|
||||
|
||||
|
@ -1120,7 +1120,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
|
|||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
for (TypeId ty : subIntersection)
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++}));
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
|
||||
return SubtypingResult::any(subtypings);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauDontInPlaceMutateTableType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowNonSharedTableTypesInLiteral)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
|
||||
|
||||
namespace Luau
|
||||
|
@ -277,19 +275,11 @@ TypeId matchLiteralType(
|
|||
|
||||
Property& prop = it->second;
|
||||
|
||||
if (FFlag::LuauAllowNonSharedTableTypesInLiteral)
|
||||
{
|
||||
// If we encounter a duplcate property, we may have already
|
||||
// set it to be read-only. If that's the case, the only thing
|
||||
// that will definitely crash is trying to access a write
|
||||
// only property.
|
||||
LUAU_ASSERT(!prop.isWriteOnly());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Table literals always initially result in shared read-write types
|
||||
LUAU_ASSERT(prop.isShared());
|
||||
}
|
||||
// If we encounter a duplcate property, we may have already
|
||||
// set it to be read-only. If that's the case, the only thing
|
||||
// that will definitely crash is trying to access a write
|
||||
// only property.
|
||||
LUAU_ASSERT(!prop.isWriteOnly());
|
||||
TypeId propTy = *prop.readTy;
|
||||
|
||||
auto it2 = expectedTableTy->props.find(keyStr);
|
||||
|
@ -322,10 +312,7 @@ TypeId matchLiteralType(
|
|||
else
|
||||
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
|
||||
|
||||
if (FFlag::LuauDontInPlaceMutateTableType)
|
||||
keysToDelete.insert(item.key->as<AstExprConstantString>());
|
||||
else
|
||||
tableTy->props.erase(keyStr);
|
||||
keysToDelete.insert(item.key->as<AstExprConstantString>());
|
||||
|
||||
}
|
||||
|
||||
|
@ -434,14 +421,11 @@ TypeId matchLiteralType(
|
|||
LUAU_ASSERT(!"Unexpected");
|
||||
}
|
||||
|
||||
if (FFlag::LuauDontInPlaceMutateTableType)
|
||||
for (const auto& key : keysToDelete)
|
||||
{
|
||||
for (const auto& key : keysToDelete)
|
||||
{
|
||||
const AstArray<char>& s = key->value;
|
||||
std::string keyStr{s.data, s.data + s.size};
|
||||
tableTy->props.erase(keyStr);
|
||||
}
|
||||
const AstArray<char>& s = key->value;
|
||||
std::string keyStr{s.data, s.data + s.size};
|
||||
tableTy->props.erase(keyStr);
|
||||
}
|
||||
|
||||
// Keys that the expectedType says we should have, but that aren't
|
||||
|
|
|
@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauStoreCSTData)
|
|||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -271,6 +272,43 @@ private:
|
|||
const Position* commaPosition;
|
||||
};
|
||||
|
||||
class ArgNameInserter
|
||||
{
|
||||
public:
|
||||
ArgNameInserter(Writer& w, AstArray<std::optional<AstArgumentName>> names, AstArray<std::optional<Position>> colonPositions)
|
||||
: writer(w)
|
||||
, names(names)
|
||||
, colonPositions(colonPositions)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
if (idx < names.size)
|
||||
{
|
||||
const auto name = names.data[idx];
|
||||
if (name.has_value())
|
||||
{
|
||||
writer.advance(name->second.begin);
|
||||
writer.identifier(name->first.value);
|
||||
if (idx < colonPositions.size)
|
||||
{
|
||||
LUAU_ASSERT(colonPositions.data[idx].has_value());
|
||||
writer.advance(*colonPositions.data[idx]);
|
||||
}
|
||||
writer.symbol(":");
|
||||
}
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
private:
|
||||
Writer& writer;
|
||||
AstArray<std::optional<AstArgumentName>> names;
|
||||
AstArray<std::optional<Position>> colonPositions;
|
||||
size_t idx = 0;
|
||||
};
|
||||
|
||||
struct Printer_DEPRECATED
|
||||
{
|
||||
explicit Printer_DEPRECATED(Writer& writer)
|
||||
|
@ -1216,6 +1254,15 @@ struct Printer_DEPRECATED
|
|||
|
||||
for (size_t i = 0; i < a->types.size; ++i)
|
||||
{
|
||||
if (FFlag::LuauParseOptionalAsNode)
|
||||
{
|
||||
if (a->types.data[i]->is<AstTypeOptional>())
|
||||
{
|
||||
writer.symbol("?");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
|
@ -1312,7 +1359,7 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg)
|
||||
void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg)
|
||||
{
|
||||
advance(annotation.location.begin);
|
||||
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
|
||||
|
@ -1322,15 +1369,22 @@ struct Printer
|
|||
|
||||
visualizeTypeAnnotation(*variadicTp->variadicType);
|
||||
}
|
||||
else if (const AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
|
||||
else if (AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
|
||||
{
|
||||
writer.symbol(genericTp->genericName.value);
|
||||
if (const auto cstNode = lookupCstNode<CstTypePackGeneric>(genericTp))
|
||||
advance(cstNode->ellipsisPosition);
|
||||
writer.symbol("...");
|
||||
}
|
||||
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
|
||||
else if (AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
|
||||
{
|
||||
LUAU_ASSERT(!forVarArg);
|
||||
visualizeTypeList(explicitTp->typeList, true);
|
||||
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
|
||||
visualizeTypeList(
|
||||
explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions
|
||||
);
|
||||
else
|
||||
visualizeTypeList(explicitTp->typeList, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1338,19 +1392,37 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
void visualizeTypeList(const AstTypeList& list, bool unconditionallyParenthesize)
|
||||
void visualizeNamedTypeList(
|
||||
const AstTypeList& list,
|
||||
bool unconditionallyParenthesize,
|
||||
std::optional<Position> openParenthesesPosition,
|
||||
std::optional<Position> closeParenthesesPosition,
|
||||
AstArray<Position> commaPositions,
|
||||
AstArray<std::optional<AstArgumentName>> argNames,
|
||||
AstArray<std::optional<Position>> argNamesColonPositions
|
||||
)
|
||||
{
|
||||
size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0);
|
||||
if (typeCount == 0)
|
||||
{
|
||||
if (openParenthesesPosition)
|
||||
advance(*openParenthesesPosition);
|
||||
writer.symbol("(");
|
||||
if (closeParenthesesPosition)
|
||||
advance(*closeParenthesesPosition);
|
||||
writer.symbol(")");
|
||||
}
|
||||
else if (typeCount == 1)
|
||||
{
|
||||
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
|
||||
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
{
|
||||
if (openParenthesesPosition)
|
||||
advance(*openParenthesesPosition);
|
||||
writer.symbol("(");
|
||||
}
|
||||
|
||||
ArgNameInserter(writer, argNames, argNamesColonPositions)();
|
||||
|
||||
// Only variadic tail
|
||||
if (list.types.size == 0)
|
||||
|
@ -1363,33 +1435,50 @@ struct Printer
|
|||
}
|
||||
|
||||
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
||||
{
|
||||
if (closeParenthesesPosition)
|
||||
advance(*closeParenthesesPosition);
|
||||
writer.symbol(")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (openParenthesesPosition)
|
||||
advance(*openParenthesesPosition);
|
||||
writer.symbol("(");
|
||||
|
||||
bool first = true;
|
||||
CommaSeparatorInserter comma(writer, commaPositions.size > 0 ? commaPositions.begin() : nullptr);
|
||||
ArgNameInserter argName(writer, argNames, argNamesColonPositions);
|
||||
for (const auto& el : list.types)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
writer.symbol(",");
|
||||
|
||||
comma();
|
||||
argName();
|
||||
visualizeTypeAnnotation(*el);
|
||||
}
|
||||
|
||||
if (list.tailType)
|
||||
{
|
||||
writer.symbol(",");
|
||||
comma();
|
||||
visualizeTypePackAnnotation(*list.tailType, false);
|
||||
}
|
||||
|
||||
if (closeParenthesesPosition)
|
||||
advance(*closeParenthesesPosition);
|
||||
writer.symbol(")");
|
||||
}
|
||||
}
|
||||
|
||||
void visualizeTypeList(
|
||||
const AstTypeList& list,
|
||||
bool unconditionallyParenthesize,
|
||||
std::optional<Position> openParenthesesPosition = std::nullopt,
|
||||
std::optional<Position> closeParenthesesPosition = std::nullopt,
|
||||
AstArray<Position> commaPositions = {}
|
||||
)
|
||||
{
|
||||
visualizeNamedTypeList(list, unconditionallyParenthesize, openParenthesesPosition, closeParenthesesPosition, commaPositions, {}, {});
|
||||
}
|
||||
|
||||
bool isIntegerish(double d)
|
||||
{
|
||||
if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min())
|
||||
|
@ -1406,7 +1495,7 @@ struct Printer
|
|||
{
|
||||
writer.symbol("(");
|
||||
visualize(*a->expr);
|
||||
advance(Position{a->location.end.line, a->location.end.column - 1});
|
||||
advanceBefore(a->location.end, 1);
|
||||
writer.symbol(")");
|
||||
}
|
||||
else if (expr.is<AstExprConstantNil>())
|
||||
|
@ -1775,6 +1864,14 @@ struct Printer
|
|||
writer.advance(newPos);
|
||||
}
|
||||
|
||||
void advanceBefore(const Position& newPos, unsigned int tokenLength)
|
||||
{
|
||||
if (newPos.column >= tokenLength)
|
||||
advance(Position{newPos.line, newPos.column - tokenLength});
|
||||
else
|
||||
advance(newPos);
|
||||
}
|
||||
|
||||
void visualize(AstStat& program)
|
||||
{
|
||||
advance(program.location.begin);
|
||||
|
@ -1817,8 +1914,8 @@ struct Printer
|
|||
visualizeBlock(*a->body);
|
||||
if (const auto cstNode = lookupCstNode<CstStatRepeat>(a))
|
||||
writer.advance(cstNode->untilPosition);
|
||||
else if (a->condition->location.begin.column > 5)
|
||||
writer.advance(Position{a->condition->location.begin.line, a->condition->location.begin.column - 6});
|
||||
else
|
||||
advanceBefore(a->condition->location.begin, 6);
|
||||
writer.keyword("until");
|
||||
visualize(*a->condition);
|
||||
}
|
||||
|
@ -2121,7 +2218,20 @@ struct Printer
|
|||
{
|
||||
if (writeTypes)
|
||||
{
|
||||
writer.keyword("type function");
|
||||
const auto cstNode = lookupCstNode<CstStatTypeFunction>(t);
|
||||
if (t->exported)
|
||||
writer.keyword("export");
|
||||
if (cstNode)
|
||||
advance(cstNode->typeKeywordPosition);
|
||||
else
|
||||
writer.space();
|
||||
writer.keyword("type");
|
||||
if (cstNode)
|
||||
advance(cstNode->functionKeywordPosition);
|
||||
else
|
||||
writer.space();
|
||||
writer.keyword("function");
|
||||
advance(t->nameLocation.begin);
|
||||
writer.identifier(t->name.value);
|
||||
visualizeFunctionBody(*t->body);
|
||||
}
|
||||
|
@ -2152,16 +2262,22 @@ struct Printer
|
|||
if (program.hasSemicolon)
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
advance(Position{program.location.end.line, program.location.end.column - 1});
|
||||
advanceBefore(program.location.end, 1);
|
||||
writer.symbol(";");
|
||||
}
|
||||
}
|
||||
|
||||
void visualizeFunctionBody(AstExprFunction& func)
|
||||
{
|
||||
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
|
||||
|
||||
// TODO(CLI-139347): need to handle attributes, argument types, and return type (incl. parentheses of return type)
|
||||
|
||||
if (func.generics.size > 0 || func.genericPacks.size > 0)
|
||||
{
|
||||
CommaSeparatorInserter comma(writer);
|
||||
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
|
||||
if (cstNode)
|
||||
advance(cstNode->openGenericsPosition);
|
||||
writer.symbol("<");
|
||||
for (const auto& o : func.generics)
|
||||
{
|
||||
|
@ -2176,13 +2292,19 @@ struct Printer
|
|||
|
||||
writer.advance(o->location.begin);
|
||||
writer.identifier(o->name.value);
|
||||
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
|
||||
advance(genericTypePackCstNode->ellipsisPosition);
|
||||
writer.symbol("...");
|
||||
}
|
||||
if (cstNode)
|
||||
advance(cstNode->closeGenericsPosition);
|
||||
writer.symbol(">");
|
||||
}
|
||||
|
||||
if (func.argLocation)
|
||||
advance(func.argLocation->begin);
|
||||
writer.symbol("(");
|
||||
CommaSeparatorInserter comma(writer);
|
||||
CommaSeparatorInserter comma(writer, cstNode ? cstNode->argsCommaPositions.begin() : nullptr);
|
||||
|
||||
for (size_t i = 0; i < func.args.size; ++i)
|
||||
{
|
||||
|
@ -2212,10 +2334,14 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
if (func.argLocation)
|
||||
advanceBefore(func.argLocation->end, 1);
|
||||
writer.symbol(")");
|
||||
|
||||
if (writeTypes && func.returnAnnotation)
|
||||
{
|
||||
if (cstNode)
|
||||
advance(cstNode->returnSpecifierPosition);
|
||||
writer.symbol(":");
|
||||
writer.space();
|
||||
|
||||
|
@ -2340,9 +2466,13 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
|
||||
{
|
||||
const auto cstNode = lookupCstNode<CstTypeFunction>(a);
|
||||
|
||||
if (a->generics.size > 0 || a->genericPacks.size > 0)
|
||||
{
|
||||
CommaSeparatorInserter comma(writer);
|
||||
CommaSeparatorInserter comma(writer, cstNode ? cstNode->genericsCommaPositions.begin() : nullptr);
|
||||
if (cstNode)
|
||||
advance(cstNode->openGenericsPosition);
|
||||
writer.symbol("<");
|
||||
for (const auto& o : a->generics)
|
||||
{
|
||||
|
@ -2357,15 +2487,29 @@ struct Printer
|
|||
|
||||
writer.advance(o->location.begin);
|
||||
writer.identifier(o->name.value);
|
||||
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
|
||||
advance(genericTypePackCstNode->ellipsisPosition);
|
||||
writer.symbol("...");
|
||||
}
|
||||
if (cstNode)
|
||||
advance(cstNode->closeGenericsPosition);
|
||||
writer.symbol(">");
|
||||
}
|
||||
|
||||
{
|
||||
visualizeTypeList(a->argTypes, true);
|
||||
visualizeNamedTypeList(
|
||||
a->argTypes,
|
||||
true,
|
||||
cstNode ? std::make_optional(cstNode->openArgsPosition) : std::nullopt,
|
||||
cstNode ? std::make_optional(cstNode->closeArgsPosition) : std::nullopt,
|
||||
cstNode ? cstNode->argumentsCommaPositions : Luau::AstArray<Position>{},
|
||||
a->argNames,
|
||||
cstNode ? cstNode->argumentNameColonPositions : Luau::AstArray<std::optional<Position>>{}
|
||||
);
|
||||
}
|
||||
|
||||
if (cstNode)
|
||||
advance(cstNode->returnArrowPosition);
|
||||
writer.symbol("->");
|
||||
visualizeTypeList(a->returnTypes, true);
|
||||
}
|
||||
|
@ -2557,6 +2701,15 @@ struct Printer
|
|||
|
||||
for (size_t i = 0; i < a->types.size; ++i)
|
||||
{
|
||||
if (FFlag::LuauParseOptionalAsNode)
|
||||
{
|
||||
if (a->types.data[i]->is<AstTypeOptional>())
|
||||
{
|
||||
writer.symbol("?");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
|
@ -2599,7 +2752,7 @@ struct Printer
|
|||
{
|
||||
writer.symbol("(");
|
||||
visualizeTypeAnnotation(*a->type);
|
||||
advance(Position{a->location.end.line, a->location.end.column - 1});
|
||||
advanceBefore(a->location.end, 1);
|
||||
writer.symbol(")");
|
||||
}
|
||||
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||
|
||||
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
|
||||
{
|
||||
char* result = (char*)allocator.allocate(contents.size() + 1);
|
||||
|
@ -305,7 +307,8 @@ public:
|
|||
std::optional<AstArgumentName>* arg = &argNames.data[i++];
|
||||
|
||||
if (el)
|
||||
new (arg) std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), el->location));
|
||||
new (arg)
|
||||
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData ? Location() : el->location));
|
||||
else
|
||||
new (arg) std::optional<AstArgumentName>();
|
||||
}
|
||||
|
|
|
@ -26,10 +26,13 @@
|
|||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
|
||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1201,7 +1204,8 @@ void TypeChecker2::visit(AstStatTypeAlias* stat)
|
|||
|
||||
void TypeChecker2::visit(AstStatTypeFunction* stat)
|
||||
{
|
||||
// TODO: add type checking for user-defined type functions
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
visit(stat->body);
|
||||
}
|
||||
|
||||
void TypeChecker2::visit(AstTypeList types)
|
||||
|
@ -2701,20 +2705,61 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
|
|||
if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp)
|
||||
ice->ice("Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack.", location);
|
||||
|
||||
std::string relation = "a subtype of";
|
||||
if (reasoning.variance == SubtypingVariance::Invariant)
|
||||
relation = "exactly";
|
||||
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
||||
relation = "a supertype of";
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
std::string relation = "a subtype of";
|
||||
if (reasoning.variance == SubtypingVariance::Invariant)
|
||||
relation = "exactly";
|
||||
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
||||
relation = "a supertype of";
|
||||
|
||||
std::string reason;
|
||||
if (reasoning.subPath == reasoning.superPath)
|
||||
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
|
||||
std::string subLeafAsString = toString(subLeaf);
|
||||
// if the string is empty, it must be an empty type pack
|
||||
if (subLeafAsString.empty())
|
||||
subLeafAsString = "()";
|
||||
|
||||
std::string superLeafAsString = toString(superLeaf);
|
||||
// if the string is empty, it must be an empty type pack
|
||||
if (superLeafAsString.empty())
|
||||
superLeafAsString = "()";
|
||||
|
||||
std::stringstream baseReasonBuilder;
|
||||
baseReasonBuilder << "`" << subLeafAsString << "` is not " << relation << " `" << superLeafAsString << "`";
|
||||
std::string baseReason = baseReasonBuilder.str();
|
||||
|
||||
std::stringstream reason;
|
||||
|
||||
if (reasoning.subPath == reasoning.superPath)
|
||||
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` in the former type and `" << superLeafAsString
|
||||
<< "` in the latter type, and " << baseReason;
|
||||
else if (!reasoning.subPath.empty() && !reasoning.superPath.empty())
|
||||
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "` and " << toStringHuman(reasoning.superPath) << "`"
|
||||
<< superLeafAsString << "`, and " << baseReason;
|
||||
else if (!reasoning.subPath.empty())
|
||||
reason << toStringHuman(reasoning.subPath) << "`" << subLeafAsString << "`, which is not " << relation << " `" << superLeafAsString
|
||||
<< "`";
|
||||
else
|
||||
reason << toStringHuman(reasoning.superPath) << "`" << superLeafAsString << "`, and " << baseReason;
|
||||
|
||||
reasons.push_back(reason.str());
|
||||
}
|
||||
else
|
||||
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " +
|
||||
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
|
||||
{
|
||||
std::string relation = "a subtype of";
|
||||
if (reasoning.variance == SubtypingVariance::Invariant)
|
||||
relation = "exactly";
|
||||
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
||||
relation = "a supertype of";
|
||||
|
||||
reasons.push_back(reason);
|
||||
std::string reason;
|
||||
if (reasoning.subPath == reasoning.superPath)
|
||||
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
|
||||
else
|
||||
reason = "type " + toString(subTy) + toString(reasoning.subPath, /* prefixDot */ true) + " (" + toString(subLeaf) + ") is not " +
|
||||
relation + " " + toString(superTy) + toString(reasoning.superPath, /* prefixDot */ true) + " (" + toString(superLeaf) + ")";
|
||||
|
||||
reasons.push_back(reason);
|
||||
}
|
||||
|
||||
// if we haven't already proved this isn't suppressing, we have to keep checking.
|
||||
if (suppressed)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/TypeFunctionReductionGuesser.h"
|
||||
#include "Luau/TypeFunctionRuntime.h"
|
||||
#include "Luau/TypeFunctionRuntimeBuilder.h"
|
||||
|
@ -49,12 +50,13 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
|
|||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotGeneralizeInTypeFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPreventReentrantTypeFunctionReduction)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -451,48 +453,29 @@ static FunctionGraphReductionResult reduceFunctionsInternal(
|
|||
TypeFunctionReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force};
|
||||
int iterationCount = 0;
|
||||
|
||||
if (FFlag::LuauPreventReentrantTypeFunctionReduction)
|
||||
// If we are reducing a type function while reducing a type function,
|
||||
// we're probably doing something clowny. One known place this can
|
||||
// occur is type function reduction => overload selection => subtyping
|
||||
// => back to type function reduction. At worst, if there's a reduction
|
||||
// that _doesn't_ loop forever and _needs_ reentrancy, we'll fail to
|
||||
// handle that and potentially emit an error when we didn't need to.
|
||||
if (ctx.normalizer->sharedState->reentrantTypeReduction)
|
||||
return {};
|
||||
|
||||
TypeReductionRentrancyGuard _{ctx.normalizer->sharedState};
|
||||
while (!reducer.done())
|
||||
{
|
||||
// If we are reducing a type function while reducing a type function,
|
||||
// we're probably doing something clowny. One known place this can
|
||||
// occur is type function reduction => overload selection => subtyping
|
||||
// => back to type function reduction. At worst, if there's a reduction
|
||||
// that _doesn't_ loop forever and _needs_ reentrancy, we'll fail to
|
||||
// handle that and potentially emit an error when we didn't need to.
|
||||
if (ctx.normalizer->sharedState->reentrantTypeReduction)
|
||||
return {};
|
||||
reducer.step();
|
||||
|
||||
TypeReductionRentrancyGuard _{ctx.normalizer->sharedState};
|
||||
while (!reducer.done())
|
||||
++iterationCount;
|
||||
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
|
||||
{
|
||||
reducer.step();
|
||||
|
||||
++iterationCount;
|
||||
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
|
||||
{
|
||||
reducer.result.errors.emplace_back(location, CodeTooComplex{});
|
||||
break;
|
||||
}
|
||||
reducer.result.errors.emplace_back(location, CodeTooComplex{});
|
||||
break;
|
||||
}
|
||||
|
||||
return std::move(reducer.result);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (!reducer.done())
|
||||
{
|
||||
reducer.step();
|
||||
|
||||
++iterationCount;
|
||||
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
|
||||
{
|
||||
reducer.result.errors.emplace_back(location, CodeTooComplex{});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return std::move(reducer.result);
|
||||
}
|
||||
return std::move(reducer.result);
|
||||
}
|
||||
|
||||
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
|
||||
|
@ -861,15 +844,6 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
|||
if (isPending(operandTy, ctx->solver))
|
||||
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 && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy, /* avoidSealingTables */ true);
|
||||
if (!maybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
||||
operandTy = *maybeGeneralized;
|
||||
}
|
||||
|
||||
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
|
||||
NormalizationResult inhabited = ctx->normalizer->isInhabited(normTy.get());
|
||||
|
||||
|
@ -953,15 +927,6 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
|||
if (isPending(operandTy, ctx->solver))
|
||||
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 && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy);
|
||||
if (!maybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
||||
operandTy = *maybeGeneralized;
|
||||
}
|
||||
|
||||
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
|
||||
|
||||
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
|
@ -1196,21 +1161,6 @@ TypeFunctionReductionResult<TypeId> numericBinopTypeFunction(
|
|||
else if (isPending(rhsTy, ctx->solver))
|
||||
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 && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
||||
if (!lhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (!rhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
|
||||
lhsTy = *lhsMaybeGeneralized;
|
||||
rhsTy = *rhsMaybeGeneralized;
|
||||
}
|
||||
|
||||
// TODO: Normalization needs to remove cyclic type functions from a `NormalizedType`.
|
||||
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||
|
@ -1433,21 +1383,6 @@ TypeFunctionReductionResult<TypeId> concatTypeFunction(
|
|||
else if (isPending(rhsTy, ctx->solver))
|
||||
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 && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
||||
if (!lhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (!rhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
|
||||
lhsTy = *lhsMaybeGeneralized;
|
||||
rhsTy = *rhsMaybeGeneralized;
|
||||
}
|
||||
|
||||
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||
|
||||
|
@ -1548,21 +1483,6 @@ TypeFunctionReductionResult<TypeId> andTypeFunction(
|
|||
else if (isPending(rhsTy, ctx->solver))
|
||||
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 && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
||||
if (!lhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (!rhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
|
||||
lhsTy = *lhsMaybeGeneralized;
|
||||
rhsTy = *rhsMaybeGeneralized;
|
||||
}
|
||||
|
||||
// And evalutes to a boolean if the LHS is falsey, and the RHS type if LHS is truthy.
|
||||
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->falsyType);
|
||||
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
|
||||
|
@ -1603,21 +1523,6 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
|
|||
else if (isPending(rhsTy, ctx->solver))
|
||||
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 && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
||||
if (!lhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (!rhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
|
||||
lhsTy = *lhsMaybeGeneralized;
|
||||
rhsTy = *rhsMaybeGeneralized;
|
||||
}
|
||||
|
||||
// Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy.
|
||||
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
|
||||
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
|
||||
|
@ -1689,21 +1594,6 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
|
|||
lhsTy = follow(lhsTy);
|
||||
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 && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
||||
if (!lhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (!rhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
|
||||
lhsTy = *lhsMaybeGeneralized;
|
||||
rhsTy = *rhsMaybeGeneralized;
|
||||
}
|
||||
|
||||
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||
|
||||
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||
|
@ -1827,21 +1717,6 @@ TypeFunctionReductionResult<TypeId> eqTypeFunction(
|
|||
else if (isPending(rhsTy, ctx->solver))
|
||||
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 && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
||||
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
||||
|
||||
if (!lhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
|
||||
else if (!rhsMaybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||
|
||||
lhsTy = *lhsMaybeGeneralized;
|
||||
rhsTy = *rhsMaybeGeneralized;
|
||||
}
|
||||
|
||||
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
|
||||
|
@ -2000,20 +1875,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
|
||||
{
|
||||
std::vector<TypeId> toBlock;
|
||||
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);
|
||||
|
||||
if (!targetMaybeGeneralized)
|
||||
return std::pair<TypeId, std::vector<TypeId>>{nullptr, {target}};
|
||||
else if (!discriminantMaybeGeneralized)
|
||||
return std::pair<TypeId, std::vector<TypeId>>{nullptr, {discriminant}};
|
||||
|
||||
target = *targetMaybeGeneralized;
|
||||
discriminant = *discriminantMaybeGeneralized;
|
||||
}
|
||||
|
||||
// we need a more complex check for blocking on the discriminant in particular
|
||||
FindRefinementBlockers frb;
|
||||
frb.traverse(discriminant);
|
||||
|
@ -2131,15 +1992,6 @@ TypeFunctionReductionResult<TypeId> singletonTypeFunction(
|
|||
if (isPending(type, ctx->solver))
|
||||
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 && !FFlag::LuauDoNotGeneralizeInTypeFunctions)
|
||||
{
|
||||
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, type);
|
||||
if (!maybeGeneralized)
|
||||
return {std::nullopt, Reduction::MaybeOk, {type}, {}};
|
||||
type = *maybeGeneralized;
|
||||
}
|
||||
|
||||
TypeId followed = type;
|
||||
// we want to follow through a negation here as well.
|
||||
if (auto negation = get<NegationType>(followed))
|
||||
|
@ -2207,73 +2059,24 @@ 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())
|
||||
{
|
||||
|
||||
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, {}, {}};
|
||||
std::vector<TypeId> blockingTypes{collector.blockingTypes.begin(), collector.blockingTypes.end()};
|
||||
return {std::nullopt, Reduction::MaybeOk, std::move(blockingTypes), {}};
|
||||
}
|
||||
|
||||
// we need to follow all of the type parameters.
|
||||
std::vector<TypeId> types;
|
||||
types.reserve(typeParams.size());
|
||||
for (auto ty : typeParams)
|
||||
types.emplace_back(follow(ty));
|
||||
|
||||
// unfortunately, we need this short-circuit: if all but one type is `never`, we will return that one type.
|
||||
// this also will early return if _everything_ is `never`, since we already have to check that.
|
||||
std::optional<TypeId> lastType = std::nullopt;
|
||||
for (auto ty : types)
|
||||
{
|
||||
// if we have a previous type and it's not `never` and the current type isn't `never`...
|
||||
if (lastType && !get<NeverType>(lastType) && !get<NeverType>(ty))
|
||||
{
|
||||
// we know we are not taking the short-circuited path.
|
||||
lastType = std::nullopt;
|
||||
break;
|
||||
}
|
||||
|
||||
if (get<NeverType>(ty))
|
||||
continue;
|
||||
lastType = ty;
|
||||
}
|
||||
|
||||
// if we still have a `lastType` at the end, we're taking the short-circuit and reducing early.
|
||||
if (lastType)
|
||||
return {lastType, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
// check to see if the operand types are resolved enough, and wait to reduce if not
|
||||
for (auto ty : types)
|
||||
if (isPending(ty, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
|
||||
|
||||
// fold over the types with `simplifyUnion`
|
||||
TypeId resultTy = ctx->builtins->neverType;
|
||||
for (auto ty : types)
|
||||
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()}, {}};
|
||||
|
||||
|
@ -2281,6 +2084,7 @@ TypeFunctionReductionResult<TypeId> unionTypeFunction(
|
|||
}
|
||||
|
||||
return {resultTy, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -2621,7 +2425,12 @@ bool searchPropsAndIndexer(
|
|||
if (auto propUnionTy = get<UnionType>(propTy))
|
||||
{
|
||||
for (TypeId option : propUnionTy->options)
|
||||
result.insert(option);
|
||||
{
|
||||
if (FFlag::LuauIndexTypeFunctionImprovements)
|
||||
result.insert(follow(option));
|
||||
else
|
||||
result.insert(option);
|
||||
}
|
||||
}
|
||||
else // property is a singular type or intersection type -> we can simply append
|
||||
result.insert(propTy);
|
||||
|
@ -2641,7 +2450,12 @@ bool searchPropsAndIndexer(
|
|||
if (auto idxResUnionTy = get<UnionType>(idxResultTy))
|
||||
{
|
||||
for (TypeId option : idxResUnionTy->options)
|
||||
result.insert(option);
|
||||
{
|
||||
if (FFlag::LuauIndexTypeFunctionImprovements)
|
||||
result.insert(follow(option));
|
||||
else
|
||||
result.insert(option);
|
||||
}
|
||||
}
|
||||
else // indexResultType is a singular type or intersection type -> we can simply append
|
||||
result.insert(idxResultTy);
|
||||
|
@ -2656,7 +2470,7 @@ bool searchPropsAndIndexer(
|
|||
/* Handles recursion / metamethods of tables/classes
|
||||
`isRaw` parameter indicates whether or not we should follow __index metamethods
|
||||
returns false if property of `ty` could not be found */
|
||||
bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
|
||||
bool tblIndexInto_DEPRECATED(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
|
||||
{
|
||||
indexer = follow(indexer);
|
||||
indexee = follow(indexee);
|
||||
|
@ -2686,13 +2500,113 @@ bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result,
|
|||
ErrorVec dummy;
|
||||
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, indexee, "__index", Location{});
|
||||
if (mmType)
|
||||
return tblIndexInto(indexer, *mmType, result, ctx, isRaw);
|
||||
return tblIndexInto_DEPRECATED(indexer, *mmType, result, ctx, isRaw);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, DenseHashSet<TypeId>& seenSet, NotNull<TypeFunctionContext> ctx, bool isRaw)
|
||||
{
|
||||
indexer = follow(indexer);
|
||||
indexee = follow(indexee);
|
||||
|
||||
if (seenSet.contains(indexee))
|
||||
return false;
|
||||
seenSet.insert(indexee);
|
||||
|
||||
if (FFlag::LuauIndexTypeFunctionFunctionMetamethods)
|
||||
{
|
||||
if (auto unionTy = get<UnionType>(indexee))
|
||||
{
|
||||
bool res = true;
|
||||
for (auto component : unionTy)
|
||||
{
|
||||
// if the component is in the seen set and isn't the indexee itself,
|
||||
// we can skip it cause it means we encountered it in an earlier component in the union.
|
||||
if (seenSet.contains(component) && component != indexee)
|
||||
continue;
|
||||
|
||||
res = res && tblIndexInto(indexer, component, result, seenSet, ctx, isRaw);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
if (get<FunctionType>(indexee))
|
||||
{
|
||||
TypePackId argPack = ctx->arena->addTypePack({indexer});
|
||||
SolveResult solveResult = solveFunctionCall(
|
||||
ctx->arena,
|
||||
ctx->builtins,
|
||||
ctx->simplifier,
|
||||
ctx->normalizer,
|
||||
ctx->typeFunctionRuntime,
|
||||
ctx->ice,
|
||||
ctx->limits,
|
||||
ctx->scope,
|
||||
ctx->scope->location,
|
||||
indexee,
|
||||
argPack
|
||||
);
|
||||
|
||||
if (!solveResult.typePackId.has_value())
|
||||
return false;
|
||||
|
||||
TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *solveResult.typePackId, 1);
|
||||
if (extracted.head.empty())
|
||||
return false;
|
||||
|
||||
result.insert(follow(extracted.head.front()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// we have a table type to try indexing
|
||||
if (auto tableTy = get<TableType>(indexee))
|
||||
{
|
||||
return searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx);
|
||||
}
|
||||
|
||||
// we have a metatable type to try indexing
|
||||
if (auto metatableTy = get<MetatableType>(indexee))
|
||||
{
|
||||
if (auto tableTy = get<TableType>(follow(metatableTy->table)))
|
||||
{
|
||||
|
||||
// try finding all properties within the current scope of the table
|
||||
if (searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx))
|
||||
return true;
|
||||
}
|
||||
|
||||
// if the code reached here, it means we weren't able to find all properties -> look into __index metamethod
|
||||
if (!isRaw)
|
||||
{
|
||||
// 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> mmType = findMetatableEntry(ctx->builtins, dummy, indexee, "__index", Location{});
|
||||
if (mmType)
|
||||
return tblIndexInto(indexer, *mmType, result, seenSet, ctx, isRaw);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
|
||||
{
|
||||
if (FFlag::LuauIndexTypeFunctionImprovements)
|
||||
{
|
||||
DenseHashSet<TypeId> seenSet{{}};
|
||||
return tblIndexInto(indexer, indexee, result, seenSet, ctx, isRaw);
|
||||
}
|
||||
else
|
||||
{
|
||||
return tblIndexInto_DEPRECATED(indexer, indexee, result, ctx, isRaw);
|
||||
}
|
||||
}
|
||||
|
||||
/* Vocabulary note: indexee refers to the type that contains the properties,
|
||||
indexer refers to the type that is used to access indexee
|
||||
Example: index<Person, "name"> => `Person` is the indexee and `"name"` is the indexer */
|
||||
|
@ -2710,6 +2624,13 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
|
|||
if (!indexeeNormTy)
|
||||
return {std::nullopt, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
if (FFlag::LuauIndexAnyIsAny)
|
||||
{
|
||||
// if the indexee is `any`, then indexing also gives us `any`.
|
||||
if (indexeeNormTy->shouldSuppressErrors())
|
||||
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
|
||||
}
|
||||
|
||||
// if we don't have either just tables or just classes, we've got nothing to index into
|
||||
if (indexeeNormTy->hasTables() == indexeeNormTy->hasClasses())
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||
|
@ -2809,17 +2730,19 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
|
|||
}
|
||||
}
|
||||
|
||||
// Call `follow()` on each element to resolve all Bound types before returning
|
||||
std::transform(
|
||||
properties.begin(),
|
||||
properties.end(),
|
||||
properties.begin(),
|
||||
[](TypeId ty)
|
||||
{
|
||||
return follow(ty);
|
||||
}
|
||||
if (!FFlag::LuauIndexTypeFunctionImprovements)
|
||||
{
|
||||
// Call `follow()` on each element to resolve all Bound types before returning
|
||||
std::transform(
|
||||
properties.begin(),
|
||||
properties.end(),
|
||||
properties.begin(),
|
||||
[](TypeId ty)
|
||||
{
|
||||
return follow(ty);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
// If the type being reduced to is a single type, no need to union
|
||||
if (properties.size() == 1)
|
||||
return {*properties.begin(), Reduction::MaybeOk, {}, {}};
|
||||
|
|
|
@ -13,10 +13,7 @@
|
|||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunFixHydratedClasses)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
|
||||
|
||||
|
@ -1617,11 +1614,8 @@ void registerTypeUserData(lua_State* L)
|
|||
// Create and register metatable for type userdata
|
||||
luaL_newmetatable(L, "type");
|
||||
|
||||
if (FFlag::LuauUserTypeFunTypeofReturnsType)
|
||||
{
|
||||
lua_pushstring(L, "type");
|
||||
lua_setfield(L, -2, "__type");
|
||||
}
|
||||
lua_pushstring(L, "type");
|
||||
lua_setfield(L, -2, "__type");
|
||||
|
||||
// Protect metatable from being changed
|
||||
lua_pushstring(L, "The metatable is locked");
|
||||
|
@ -1758,14 +1752,14 @@ bool areEqual(SeenSet& seen, const TypeFunctionSingletonType& lhs, const TypeFun
|
|||
|
||||
{
|
||||
const TypeFunctionBooleanSingleton* lp = get<TypeFunctionBooleanSingleton>(&lhs);
|
||||
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
|
||||
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(&rhs);
|
||||
if (lp && rp)
|
||||
return lp->value == rp->value;
|
||||
}
|
||||
|
||||
{
|
||||
const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs);
|
||||
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
|
||||
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(&rhs);
|
||||
if (lp && rp)
|
||||
return lp->value == rp->value;
|
||||
}
|
||||
|
@ -1918,10 +1912,7 @@ bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctio
|
|||
if (seenSetContains(seen, &lhs, &rhs))
|
||||
return true;
|
||||
|
||||
if (FFlag::LuauTypeFunFixHydratedClasses)
|
||||
return lhs.classTy == rhs.classTy;
|
||||
else
|
||||
return lhs.name_DEPRECATED == rhs.name_DEPRECATED;
|
||||
return lhs.classTy == rhs.classTy;
|
||||
}
|
||||
|
||||
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
// used to control the recursion limit of any operations done by user-defined type functions
|
||||
// currently, controls serialization, deserialization, and `type.copy`
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
|
||||
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
|
||||
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
|
||||
|
||||
namespace Luau
|
||||
|
@ -209,19 +208,11 @@ private:
|
|||
}
|
||||
else if (auto c = get<ClassType>(ty))
|
||||
{
|
||||
if (FFlag::LuauTypeFunFixHydratedClasses)
|
||||
{
|
||||
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the
|
||||
// original class
|
||||
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty});
|
||||
}
|
||||
else
|
||||
{
|
||||
state->classesSerialized_DEPRECATED[c->name] = ty;
|
||||
target = typeFunctionRuntime->typeArena.allocate(
|
||||
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, /* classTy */ nullptr, c->name}
|
||||
);
|
||||
}
|
||||
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original
|
||||
// class
|
||||
target = typeFunctionRuntime->typeArena.allocate(
|
||||
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
|
||||
);
|
||||
}
|
||||
else if (auto g = get<GenericType>(ty))
|
||||
{
|
||||
|
@ -713,17 +704,7 @@ private:
|
|||
}
|
||||
else if (auto c = get<TypeFunctionClassType>(ty))
|
||||
{
|
||||
if (FFlag::LuauTypeFunFixHydratedClasses)
|
||||
{
|
||||
target = c->classTy;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto result = state->classesSerialized_DEPRECATED.find(c->name_DEPRECATED))
|
||||
target = *result;
|
||||
else
|
||||
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized");
|
||||
}
|
||||
target = c->classTy;
|
||||
}
|
||||
else if (auto g = get<TypeFunctionGenericType>(ty))
|
||||
{
|
||||
|
|
|
@ -5721,6 +5721,10 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
|
|||
TypeId ty = checkExpr(scope, *typeOf->expr).type;
|
||||
return ty;
|
||||
}
|
||||
else if (annotation.is<AstTypeOptional>())
|
||||
{
|
||||
return builtinTypes->nilType;
|
||||
}
|
||||
else if (const auto& un = annotation.as<AstTypeUnion>())
|
||||
{
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
#include <type_traits>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode);
|
||||
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode)
|
||||
|
||||
// Maximum number of steps to follow when traversing a path. May not always
|
||||
// equate to the number of components in a path, depending on the traversal
|
||||
// logic.
|
||||
|
@ -638,6 +639,247 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
|
|||
return result.str();
|
||||
}
|
||||
|
||||
std::string toStringHuman(const TypePath::Path& path)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSolverV2);
|
||||
|
||||
enum class State
|
||||
{
|
||||
Initial,
|
||||
Normal,
|
||||
Property,
|
||||
PendingIs,
|
||||
PendingAs,
|
||||
PendingWhich,
|
||||
};
|
||||
|
||||
std::stringstream result;
|
||||
State state = State::Initial;
|
||||
bool last = false;
|
||||
|
||||
auto strComponent = [&](auto&& c)
|
||||
{
|
||||
using T = std::decay_t<decltype(c)>;
|
||||
if constexpr (std::is_same_v<T, TypePath::Property>)
|
||||
{
|
||||
if (state == State::PendingIs)
|
||||
result << ", ";
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case State::Initial:
|
||||
case State::PendingIs:
|
||||
if (c.isRead)
|
||||
result << "accessing `";
|
||||
else
|
||||
result << "writing to `";
|
||||
break;
|
||||
case State::Property:
|
||||
// if the previous state was a property, then we're doing a sequence of indexing
|
||||
result << '.';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
result << c.name;
|
||||
|
||||
state = State::Property;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypePath::Index>)
|
||||
{
|
||||
size_t humanIndex = c.index + 1;
|
||||
|
||||
if (state == State::Initial && !last)
|
||||
result << "in" << ' ';
|
||||
else if (state == State::PendingIs)
|
||||
result << ' ' << "has" << ' ';
|
||||
else if (state == State::Property)
|
||||
result << '`' << ' ' << "has" << ' ';
|
||||
|
||||
result << "the " << humanIndex;
|
||||
switch (humanIndex)
|
||||
{
|
||||
case 1:
|
||||
result << "st";
|
||||
break;
|
||||
case 2:
|
||||
result << "nd";
|
||||
break;
|
||||
case 3:
|
||||
result << "rd";
|
||||
break;
|
||||
default:
|
||||
result << "th";
|
||||
}
|
||||
|
||||
switch (c.variant)
|
||||
{
|
||||
case TypePath::Index::Variant::Pack:
|
||||
result << ' ' << "entry in the type pack";
|
||||
break;
|
||||
case TypePath::Index::Variant::Union:
|
||||
result << ' ' << "component of the union";
|
||||
break;
|
||||
case TypePath::Index::Variant::Intersection:
|
||||
result << ' ' << "component of the intersection";
|
||||
break;
|
||||
}
|
||||
|
||||
if (state == State::PendingWhich)
|
||||
result << ' ' << "which";
|
||||
|
||||
if (state == State::PendingIs || state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypePath::TypeField>)
|
||||
{
|
||||
if (state == State::Initial && !last)
|
||||
result << "in" << ' ';
|
||||
else if (state == State::PendingIs)
|
||||
result << ", ";
|
||||
else if (state == State::Property)
|
||||
result << '`' << ' ' << "has" << ' ';
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case TypePath::TypeField::Table:
|
||||
result << "the table portion";
|
||||
if (state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
break;
|
||||
case TypePath::TypeField::Metatable:
|
||||
result << "the metatable portion";
|
||||
if (state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
break;
|
||||
case TypePath::TypeField::LowerBound:
|
||||
result << "the lower bound of" << ' ';
|
||||
state = State::Normal;
|
||||
break;
|
||||
case TypePath::TypeField::UpperBound:
|
||||
result << "the upper bound of" << ' ';
|
||||
state = State::Normal;
|
||||
break;
|
||||
case TypePath::TypeField::IndexLookup:
|
||||
result << "the index type";
|
||||
if (state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
break;
|
||||
case TypePath::TypeField::IndexResult:
|
||||
result << "the result of indexing";
|
||||
if (state == State::Property)
|
||||
state = State::PendingAs;
|
||||
else
|
||||
state = State::PendingIs;
|
||||
break;
|
||||
case TypePath::TypeField::Negated:
|
||||
result << "the negation" << ' ';
|
||||
state = State::Normal;
|
||||
break;
|
||||
case TypePath::TypeField::Variadic:
|
||||
result << "the variadic" << ' ';
|
||||
state = State::Normal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypePath::PackField>)
|
||||
{
|
||||
if (state == State::PendingIs)
|
||||
result << ", ";
|
||||
else if (state == State::Property)
|
||||
result << "`, ";
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case TypePath::PackField::Arguments:
|
||||
if (state == State::Initial)
|
||||
result << "it" << ' ';
|
||||
else if (state == State::PendingIs)
|
||||
result << "the function" << ' ';
|
||||
|
||||
result << "takes";
|
||||
break;
|
||||
case TypePath::PackField::Returns:
|
||||
if (state == State::Initial)
|
||||
result << "it" << ' ';
|
||||
else if (state == State::PendingIs)
|
||||
result << "the function" << ' ';
|
||||
|
||||
result << "returns";
|
||||
break;
|
||||
case TypePath::PackField::Tail:
|
||||
if (state == State::Initial)
|
||||
result << "it has" << ' ';
|
||||
result << "a tail of";
|
||||
break;
|
||||
}
|
||||
|
||||
if (state == State::PendingIs)
|
||||
{
|
||||
result << ' ';
|
||||
state = State::PendingWhich;
|
||||
}
|
||||
else
|
||||
{
|
||||
result << ' ';
|
||||
state = State::Normal;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TypePath::Reduction>)
|
||||
{
|
||||
if (state == State::Initial)
|
||||
result << "it" << ' ';
|
||||
result << "reduces to" << ' ';
|
||||
state = State::Normal;
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(always_false_v<T>, "Unhandled Component variant");
|
||||
}
|
||||
};
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
for (const TypePath::Component& component : path.components)
|
||||
{
|
||||
count++;
|
||||
if (count == path.components.size())
|
||||
last = true;
|
||||
|
||||
Luau::visit(strComponent, component);
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case State::Property:
|
||||
result << "` results in ";
|
||||
break;
|
||||
case State::PendingWhich:
|
||||
// pending `which` becomes `is` if it's at the end
|
||||
result << "is" << ' ';
|
||||
break;
|
||||
case State::PendingIs:
|
||||
result << ' ' << "is" << ' ';
|
||||
break;
|
||||
case State::PendingAs:
|
||||
result << ' ' << "as" << ' ';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result.str();
|
||||
}
|
||||
|
||||
static bool traverse(TraversalState& state, const Path& path)
|
||||
{
|
||||
auto step = [&state](auto&& c)
|
||||
|
|
|
@ -24,6 +24,7 @@ const size_t kPageSize = sysconf(_SC_PAGESIZE);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
|
|
|
@ -677,7 +677,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
DenseHashSet<const void*> seenPositive{nullptr};
|
||||
DenseHashSet<const void*> seenNegative{nullptr};
|
||||
|
||||
bool seenWithPolarity(const void* ty)
|
||||
bool seenWithCurrentPolarity(const void* ty)
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
|
@ -719,7 +719,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (seenWithPolarity(ty))
|
||||
if (seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
LUAU_ASSERT(ty);
|
||||
|
@ -728,7 +728,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const FreeType& ft) override
|
||||
{
|
||||
if (seenWithPolarity(ty))
|
||||
if (seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
if (!subsumes(scope, ft.scope))
|
||||
|
@ -753,7 +753,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const TableType& tt) override
|
||||
{
|
||||
if (seenWithPolarity(ty))
|
||||
if (seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
|
||||
|
@ -799,7 +799,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const FunctionType& ft) override
|
||||
{
|
||||
if (seenWithPolarity(ty))
|
||||
if (seenWithCurrentPolarity(ty))
|
||||
return false;
|
||||
|
||||
flip();
|
||||
|
@ -818,7 +818,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||
{
|
||||
if (seenWithPolarity(tp))
|
||||
if (seenWithCurrentPolarity(tp))
|
||||
return false;
|
||||
|
||||
if (!subsumes(scope, ftp.scope))
|
||||
|
|
|
@ -1127,6 +1127,16 @@ public:
|
|||
AstExpr* expr;
|
||||
};
|
||||
|
||||
class AstTypeOptional : public AstType
|
||||
{
|
||||
public:
|
||||
LUAU_RTTI(AstTypeOptional)
|
||||
|
||||
AstTypeOptional(const Location& location);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
};
|
||||
|
||||
class AstTypeUnion : public AstType
|
||||
{
|
||||
public:
|
||||
|
@ -1488,6 +1498,10 @@ public:
|
|||
{
|
||||
return visit(static_cast<AstType*>(node));
|
||||
}
|
||||
virtual bool visit(class AstTypeOptional* node)
|
||||
{
|
||||
return visit(static_cast<AstType*>(node));
|
||||
}
|
||||
virtual bool visit(class AstTypeUnion* node)
|
||||
{
|
||||
return visit(static_cast<AstType*>(node));
|
||||
|
|
|
@ -105,6 +105,20 @@ public:
|
|||
Position closeBracketPosition;
|
||||
};
|
||||
|
||||
class CstExprFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstExprFunction)
|
||||
|
||||
CstExprFunction();
|
||||
|
||||
Position openGenericsPosition{0,0};
|
||||
AstArray<Position> genericsCommaPositions;
|
||||
Position closeGenericsPosition{0,0};
|
||||
AstArray<Position> argsCommaPositions;
|
||||
Position returnSpecifierPosition{0,0};
|
||||
};
|
||||
|
||||
class CstExprTable : public CstNode
|
||||
{
|
||||
public:
|
||||
|
@ -311,6 +325,17 @@ public:
|
|||
Position equalsPosition;
|
||||
};
|
||||
|
||||
class CstStatTypeFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstStatTypeFunction)
|
||||
|
||||
CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition);
|
||||
|
||||
Position typeKeywordPosition;
|
||||
Position functionKeywordPosition;
|
||||
};
|
||||
|
||||
class CstTypeReference : public CstNode
|
||||
{
|
||||
public:
|
||||
|
@ -359,6 +384,32 @@ public:
|
|||
bool isArray = false;
|
||||
};
|
||||
|
||||
class CstTypeFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypeFunction)
|
||||
|
||||
CstTypeFunction(
|
||||
Position openGenericsPosition,
|
||||
AstArray<Position> genericsCommaPositions,
|
||||
Position closeGenericsPosition,
|
||||
Position openArgsPosition,
|
||||
AstArray<std::optional<Position>> argumentNameColonPositions,
|
||||
AstArray<Position> argumentsCommaPositions,
|
||||
Position closeArgsPosition,
|
||||
Position returnArrowPosition
|
||||
);
|
||||
|
||||
Position openGenericsPosition;
|
||||
AstArray<Position> genericsCommaPositions;
|
||||
Position closeGenericsPosition;
|
||||
Position openArgsPosition;
|
||||
AstArray<std::optional<Position>> argumentNameColonPositions;
|
||||
AstArray<Position> argumentsCommaPositions;
|
||||
Position closeArgsPosition;
|
||||
Position returnArrowPosition;
|
||||
};
|
||||
|
||||
class CstTypeTypeof : public CstNode
|
||||
{
|
||||
public:
|
||||
|
@ -382,4 +433,26 @@ public:
|
|||
unsigned int blockDepth;
|
||||
};
|
||||
|
||||
class CstTypePackExplicit : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypePackExplicit)
|
||||
|
||||
CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions);
|
||||
|
||||
Position openParenthesesPosition;
|
||||
Position closeParenthesesPosition;
|
||||
AstArray<Position> commaPositions;
|
||||
};
|
||||
|
||||
class CstTypePackGeneric : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypePackGeneric)
|
||||
|
||||
explicit CstTypePackGeneric(Position ellipsisPosition);
|
||||
|
||||
Position ellipsisPosition;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -155,7 +155,7 @@ private:
|
|||
AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition);
|
||||
|
||||
// type function Name ... end
|
||||
AstStat* parseTypeFunction(const Location& start, bool exported);
|
||||
AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition);
|
||||
|
||||
AstDeclaredClassProp parseDeclaredClassMethod();
|
||||
|
||||
|
@ -192,7 +192,8 @@ private:
|
|||
std::tuple<bool, Location, AstTypePack*> parseBindingList(
|
||||
TempVector<Binding>& result,
|
||||
bool allowDot3 = false,
|
||||
TempVector<Position>* commaPositions = nullptr
|
||||
AstArray<Position>* commaPositions = nullptr,
|
||||
std::optional<Position> initialCommaPosition = std::nullopt
|
||||
);
|
||||
|
||||
AstType* parseOptionalType();
|
||||
|
@ -209,9 +210,14 @@ private:
|
|||
// | `(' [TypeList] `)' `->` ReturnType
|
||||
|
||||
// Returns the variadic annotation, if it exists.
|
||||
AstTypePack* parseTypeList(TempVector<AstType*>& result, TempVector<std::optional<AstArgumentName>>& resultNames);
|
||||
AstTypePack* parseTypeList(
|
||||
TempVector<AstType*>& result,
|
||||
TempVector<std::optional<AstArgumentName>>& resultNames,
|
||||
TempVector<Position>* commaPositions = nullptr,
|
||||
TempVector<std::optional<Position>>* nameColonPositions = nullptr
|
||||
);
|
||||
|
||||
std::optional<AstTypeList> parseOptionalReturnType();
|
||||
std::optional<AstTypeList> parseOptionalReturnType(Position* returnSpecifierPosition = nullptr);
|
||||
std::pair<Location, AstTypeList> parseReturnType();
|
||||
|
||||
struct TableIndexerResult
|
||||
|
@ -305,7 +311,7 @@ private:
|
|||
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList(
|
||||
bool withDefaultValues,
|
||||
Position* openPosition = nullptr,
|
||||
TempVector<Position>* commaPositions = nullptr,
|
||||
AstArray<Position>* commaPositions = nullptr,
|
||||
Position* closePosition = nullptr
|
||||
);
|
||||
|
||||
|
@ -491,6 +497,7 @@ private:
|
|||
std::vector<AstGenericTypePack*> scratchGenericTypePacks;
|
||||
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
||||
std::vector<Position> scratchPosition;
|
||||
std::vector<std::optional<Position>> scratchOptPosition;
|
||||
std::string scratchData;
|
||||
|
||||
CstNodeMap cstNodeMap;
|
||||
|
|
|
@ -1069,6 +1069,16 @@ void AstTypeTypeof::visit(AstVisitor* visitor)
|
|||
expr->visit(visitor);
|
||||
}
|
||||
|
||||
AstTypeOptional::AstTypeOptional(const Location& location)
|
||||
: AstType(ClassIndex(), location)
|
||||
{
|
||||
}
|
||||
|
||||
void AstTypeOptional::visit(AstVisitor* visitor)
|
||||
{
|
||||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstTypeUnion::AstTypeUnion(const Location& location, const AstArray<AstType*>& types)
|
||||
: AstType(ClassIndex(), location)
|
||||
, types(types)
|
||||
|
|
|
@ -38,6 +38,10 @@ CstExprIndexExpr::CstExprIndexExpr(Position openBracketPosition, Position closeB
|
|||
{
|
||||
}
|
||||
|
||||
CstExprFunction::CstExprFunction() : CstNode(CstClassIndex())
|
||||
{
|
||||
}
|
||||
|
||||
CstExprTable::CstExprTable(const AstArray<Item>& items)
|
||||
: CstNode(CstClassIndex())
|
||||
, items(items)
|
||||
|
@ -160,6 +164,13 @@ CstStatTypeAlias::CstStatTypeAlias(
|
|||
{
|
||||
}
|
||||
|
||||
CstStatTypeFunction::CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, typeKeywordPosition(typeKeywordPosition)
|
||||
, functionKeywordPosition(functionKeywordPosition)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypeReference::CstTypeReference(
|
||||
std::optional<Position> prefixPointPosition,
|
||||
Position openParametersPosition,
|
||||
|
@ -181,6 +192,28 @@ CstTypeTable::CstTypeTable(AstArray<Item> items, bool isArray)
|
|||
{
|
||||
}
|
||||
|
||||
CstTypeFunction::CstTypeFunction(
|
||||
Position openGenericsPosition,
|
||||
AstArray<Position> genericsCommaPositions,
|
||||
Position closeGenericsPosition,
|
||||
Position openArgsPosition,
|
||||
AstArray<std::optional<Position>> argumentNameColonPositions,
|
||||
AstArray<Position> argumentsCommaPositions,
|
||||
Position closeArgsPosition,
|
||||
Position returnArrowPosition
|
||||
)
|
||||
: CstNode(CstClassIndex())
|
||||
, openGenericsPosition(openGenericsPosition)
|
||||
, genericsCommaPositions(genericsCommaPositions)
|
||||
, closeGenericsPosition(closeGenericsPosition)
|
||||
, openArgsPosition(openArgsPosition)
|
||||
, argumentNameColonPositions(argumentNameColonPositions)
|
||||
, argumentsCommaPositions(argumentsCommaPositions)
|
||||
, closeArgsPosition(closeArgsPosition)
|
||||
, returnArrowPosition(returnArrowPosition)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, openPosition(openPosition)
|
||||
|
@ -197,4 +230,18 @@ CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstE
|
|||
LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp);
|
||||
}
|
||||
|
||||
CstTypePackExplicit::CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray<Position> commaPositions)
|
||||
: CstNode(CstClassIndex())
|
||||
, openParenthesesPosition(openParenthesesPosition)
|
||||
, closeParenthesesPosition(closeParenthesesPosition)
|
||||
, commaPositions(commaPositions)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypePackGeneric::CstTypePackGeneric(Position ellipsisPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, ellipsisPosition(ellipsisPosition)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LexerFixInterpStringStart)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -789,7 +787,7 @@ Lexeme Lexer::readNext()
|
|||
return Lexeme(Location(start, 1), '}');
|
||||
}
|
||||
|
||||
return readInterpolatedStringSection(FFlag::LexerFixInterpStringStart ? start : position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
|
||||
return readInterpolatedStringSection(start, Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
|
||||
}
|
||||
|
||||
case '=':
|
||||
|
|
|
@ -27,6 +27,7 @@ LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType
|
|||
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -647,16 +648,16 @@ AstStat* Parser::parseFor()
|
|||
else
|
||||
{
|
||||
TempVector<Binding> names(scratchBinding);
|
||||
TempVector<Position> varsCommaPosition(scratchPosition);
|
||||
AstArray<Position> varsCommaPosition;
|
||||
names.push_back(varname);
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData && options.storeCstData)
|
||||
{
|
||||
varsCommaPosition.push_back(lexer.current().location.begin);
|
||||
Position initialCommaPosition = lexer.current().location.begin;
|
||||
nextLexeme();
|
||||
parseBindingList(names, false, &varsCommaPosition);
|
||||
parseBindingList(names, false, &varsCommaPosition, initialCommaPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -701,7 +702,7 @@ AstStat* Parser::parseFor()
|
|||
AstStatForIn* node =
|
||||
allocator.alloc<AstStatForIn>(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location);
|
||||
if (options.storeCstData)
|
||||
cstNodeMap[node] = allocator.alloc<CstStatForIn>(copy(varsCommaPosition), copy(valuesCommaPositions));
|
||||
cstNodeMap[node] = allocator.alloc<CstStatForIn>(varsCommaPosition, copy(valuesCommaPositions));
|
||||
return node;
|
||||
}
|
||||
else
|
||||
|
@ -957,7 +958,7 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
|
|||
matchRecoveryStopOnToken['=']++;
|
||||
|
||||
TempVector<Binding> names(scratchBinding);
|
||||
TempVector<Position> varsCommaPositions(scratchPosition);
|
||||
AstArray<Position> varsCommaPositions;
|
||||
if (FFlag::LuauStoreCSTData && options.storeCstData)
|
||||
parseBindingList(names, false, &varsCommaPositions);
|
||||
else
|
||||
|
@ -990,7 +991,7 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
|
|||
{
|
||||
AstStatLocal* node = allocator.alloc<AstStatLocal>(Location(start, end), copy(vars), copy(values), equalsSignLocation);
|
||||
if (options.storeCstData)
|
||||
cstNodeMap[node] = allocator.alloc<CstStatLocal>(copy(varsCommaPositions), copy(valuesCommaPositions));
|
||||
cstNodeMap[node] = allocator.alloc<CstStatLocal>(varsCommaPositions, copy(valuesCommaPositions));
|
||||
return node;
|
||||
}
|
||||
else
|
||||
|
@ -1033,7 +1034,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position t
|
|||
{
|
||||
// parsing a type function
|
||||
if (lexer.current().type == Lexeme::ReservedFunction)
|
||||
return parseTypeFunction(start, exported);
|
||||
return parseTypeFunction(start, exported, typeKeywordPosition);
|
||||
|
||||
// parsing a type alias
|
||||
|
||||
|
@ -1046,7 +1047,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position t
|
|||
name = Name(nameError, lexer.current().location);
|
||||
|
||||
Position genericsOpenPosition{0, 0};
|
||||
TempVector<Position> genericsCommaPositions(scratchPosition);
|
||||
AstArray<Position> genericsCommaPositions;
|
||||
Position genericsClosePosition{0, 0};
|
||||
auto [generics, genericPacks] = FFlag::LuauStoreCSTData && options.storeCstData
|
||||
? parseGenericTypeList(
|
||||
|
@ -1065,7 +1066,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position t
|
|||
allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported);
|
||||
if (options.storeCstData)
|
||||
cstNodeMap[node] = allocator.alloc<CstStatTypeAlias>(
|
||||
typeKeywordPosition, genericsOpenPosition, copy(genericsCommaPositions), genericsClosePosition, equalsPosition
|
||||
typeKeywordPosition, genericsOpenPosition, genericsCommaPositions, genericsClosePosition, equalsPosition
|
||||
);
|
||||
return node;
|
||||
}
|
||||
|
@ -1076,7 +1077,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position t
|
|||
}
|
||||
|
||||
// type function Name `(' arglist `)' `=' funcbody `end'
|
||||
AstStat* Parser::parseTypeFunction(const Location& start, bool exported)
|
||||
AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition)
|
||||
{
|
||||
Lexeme matchFn = lexer.current();
|
||||
nextLexeme();
|
||||
|
@ -1097,7 +1098,18 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported)
|
|||
|
||||
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
|
||||
|
||||
return allocator.alloc<AstStatTypeFunction>(Location(start, body->location), fnName->name, fnName->location, body, exported);
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
AstStatTypeFunction* node =
|
||||
allocator.alloc<AstStatTypeFunction>(Location(start, body->location), fnName->name, fnName->location, body, exported);
|
||||
if (options.storeCstData)
|
||||
cstNodeMap[node] = allocator.alloc<CstStatTypeFunction>(typeKeywordPosition, matchFn.location.begin);
|
||||
return node;
|
||||
}
|
||||
else
|
||||
{
|
||||
return allocator.alloc<AstStatTypeFunction>(Location(start, body->location), fnName->name, fnName->location, body, exported);
|
||||
}
|
||||
}
|
||||
|
||||
AstDeclaredClassProp Parser::parseDeclaredClassMethod()
|
||||
|
@ -1439,7 +1451,15 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
|||
{
|
||||
Location start = matchFunction.location;
|
||||
|
||||
auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ false);
|
||||
auto* cstNode = FFlag::LuauStoreCSTData && options.storeCstData ? allocator.alloc<CstExprFunction>() : nullptr;
|
||||
|
||||
auto [generics, genericPacks] = FFlag::LuauStoreCSTData && cstNode ? parseGenericTypeList(
|
||||
/* withDefaultValues= */ false,
|
||||
&cstNode->openGenericsPosition,
|
||||
&cstNode->genericsCommaPositions,
|
||||
&cstNode->closeGenericsPosition
|
||||
)
|
||||
: parseGenericTypeList(/* withDefaultValues= */ false);
|
||||
|
||||
MatchLexeme matchParen = lexer.current();
|
||||
expectAndConsume('(', "function");
|
||||
|
@ -1464,7 +1484,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
|||
AstTypePack* varargAnnotation = nullptr;
|
||||
|
||||
if (lexer.current().type != ')')
|
||||
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
|
||||
std::tie(vararg, varargLocation, varargAnnotation) =
|
||||
parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr);
|
||||
|
||||
std::optional<Location> argLocation;
|
||||
|
||||
|
@ -1476,7 +1497,7 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
|||
if (FFlag::LuauErrorRecoveryForTableTypes)
|
||||
matchRecoveryStopOnToken[')']--;
|
||||
|
||||
std::optional<AstTypeList> typelist = parseOptionalReturnType();
|
||||
std::optional<AstTypeList> typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
|
||||
|
||||
AstLocal* funLocal = nullptr;
|
||||
|
||||
|
@ -1503,8 +1524,9 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
|||
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
|
||||
body->hasEnd = hasEnd;
|
||||
|
||||
return {
|
||||
allocator.alloc<AstExprFunction>(
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
AstExprFunction* node = allocator.alloc<AstExprFunction>(
|
||||
Location(start, end),
|
||||
attributes,
|
||||
generics,
|
||||
|
@ -1519,9 +1541,34 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
|||
typelist,
|
||||
varargAnnotation,
|
||||
argLocation
|
||||
),
|
||||
funLocal
|
||||
};
|
||||
);
|
||||
if (options.storeCstData)
|
||||
cstNodeMap[node] = cstNode;
|
||||
|
||||
return {node, funLocal};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {
|
||||
allocator.alloc<AstExprFunction>(
|
||||
Location(start, end),
|
||||
attributes,
|
||||
generics,
|
||||
genericPacks,
|
||||
self,
|
||||
vars,
|
||||
vararg,
|
||||
varargLocation,
|
||||
body,
|
||||
functionStack.size(),
|
||||
debugname,
|
||||
typelist,
|
||||
varargAnnotation,
|
||||
argLocation
|
||||
),
|
||||
funLocal
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// explist ::= {exp `,'} exp
|
||||
|
@ -1559,8 +1606,13 @@ Parser::Binding Parser::parseBinding()
|
|||
}
|
||||
|
||||
// bindinglist ::= (binding | `...') [`,' bindinglist]
|
||||
std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(TempVector<Binding>& result, bool allowDot3, TempVector<Position>* commaPositions)
|
||||
std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(TempVector<Binding>& result, bool allowDot3, AstArray<Position>* commaPositions, std::optional<Position> initialCommaPosition)
|
||||
{
|
||||
TempVector<Position> localCommaPositions(scratchPosition);
|
||||
|
||||
if (FFlag::LuauStoreCSTData && commaPositions && initialCommaPosition)
|
||||
localCommaPositions.push_back(*initialCommaPosition);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (lexer.current().type == Lexeme::Dot3 && allowDot3)
|
||||
|
@ -1575,6 +1627,9 @@ std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(TempVector<Bin
|
|||
tailAnnotation = parseVariadicArgumentTypePack();
|
||||
}
|
||||
|
||||
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||
*commaPositions = copy(localCommaPositions);
|
||||
|
||||
return {true, varargLocation, tailAnnotation};
|
||||
}
|
||||
|
||||
|
@ -1583,10 +1638,13 @@ std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(TempVector<Bin
|
|||
if (lexer.current().type != ',')
|
||||
break;
|
||||
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||
commaPositions->push_back(lexer.current().location.begin);
|
||||
localCommaPositions.push_back(lexer.current().location.begin);
|
||||
nextLexeme();
|
||||
}
|
||||
|
||||
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||
*commaPositions = copy(localCommaPositions);
|
||||
|
||||
return {false, Location(), nullptr};
|
||||
}
|
||||
|
||||
|
@ -1602,7 +1660,12 @@ AstType* Parser::parseOptionalType()
|
|||
}
|
||||
|
||||
// TypeList ::= Type [`,' TypeList] | ...Type
|
||||
AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std::optional<AstArgumentName>>& resultNames)
|
||||
AstTypePack* Parser::parseTypeList(
|
||||
TempVector<AstType*>& result,
|
||||
TempVector<std::optional<AstArgumentName>>& resultNames,
|
||||
TempVector<Position>* commaPositions,
|
||||
TempVector<std::optional<Position>>* nameColonPositions
|
||||
)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
|
@ -1614,22 +1677,33 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
|
|||
// Fill in previous argument names with empty slots
|
||||
while (resultNames.size() < result.size())
|
||||
resultNames.push_back({});
|
||||
if (FFlag::LuauStoreCSTData && nameColonPositions)
|
||||
{
|
||||
while (nameColonPositions->size() < result.size())
|
||||
nameColonPositions->push_back({});
|
||||
}
|
||||
|
||||
resultNames.push_back(AstArgumentName{AstName(lexer.current().name), lexer.current().location});
|
||||
nextLexeme();
|
||||
|
||||
if (FFlag::LuauStoreCSTData && nameColonPositions)
|
||||
nameColonPositions->push_back(lexer.current().location.begin);
|
||||
expectAndConsume(':');
|
||||
}
|
||||
else if (!resultNames.empty())
|
||||
{
|
||||
// If we have a type with named arguments, provide elements for all types
|
||||
resultNames.push_back({});
|
||||
if (FFlag::LuauStoreCSTData && nameColonPositions)
|
||||
nameColonPositions->push_back({});
|
||||
}
|
||||
|
||||
result.push_back(parseType());
|
||||
if (lexer.current().type != ',')
|
||||
break;
|
||||
|
||||
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||
commaPositions->push_back(lexer.current().location.begin);
|
||||
nextLexeme();
|
||||
|
||||
if (lexer.current().type == ')')
|
||||
|
@ -1642,13 +1716,15 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<AstTypeList> Parser::parseOptionalReturnType()
|
||||
std::optional<AstTypeList> Parser::parseOptionalReturnType(Position* returnSpecifierPosition)
|
||||
{
|
||||
if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow)
|
||||
{
|
||||
if (lexer.current().type == Lexeme::SkinnyArrow)
|
||||
report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'");
|
||||
|
||||
if (FFlag::LuauStoreCSTData && returnSpecifierPosition)
|
||||
*returnSpecifierPosition = lexer.current().location.begin;
|
||||
nextLexeme();
|
||||
|
||||
unsigned int oldRecursionCount = recursionCounter;
|
||||
|
@ -2016,7 +2092,14 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
|
|||
|
||||
Lexeme begin = lexer.current();
|
||||
|
||||
auto [generics, genericPacks] = parseGenericTypeList(/* withDefaultValues= */ false);
|
||||
Position genericsOpenPosition{0, 0};
|
||||
AstArray<Position> genericsCommaPositions;
|
||||
Position genericsClosePosition{0, 0};
|
||||
auto [generics, genericPacks] = FFlag::LuauStoreCSTData && options.storeCstData
|
||||
? parseGenericTypeList(
|
||||
/* withDefaultValues= */ false, &genericsOpenPosition, &genericsCommaPositions, &genericsClosePosition
|
||||
)
|
||||
: parseGenericTypeList(/* withDefaultValues= */ false);
|
||||
|
||||
Lexeme parameterStart = lexer.current();
|
||||
|
||||
|
@ -2026,10 +2109,17 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
|
|||
|
||||
TempVector<AstType*> params(scratchType);
|
||||
TempVector<std::optional<AstArgumentName>> names(scratchOptArgName);
|
||||
TempVector<std::optional<Position>> nameColonPositions(scratchOptPosition);
|
||||
TempVector<Position> argCommaPositions(scratchPosition);
|
||||
AstTypePack* varargAnnotation = nullptr;
|
||||
|
||||
if (lexer.current().type != ')')
|
||||
varargAnnotation = parseTypeList(params, names);
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData && options.storeCstData)
|
||||
varargAnnotation = parseTypeList(params, names, &argCommaPositions, &nameColonPositions);
|
||||
else
|
||||
varargAnnotation = parseTypeList(params, names);
|
||||
}
|
||||
|
||||
Location closeArgsLocation = lexer.current().location;
|
||||
expectMatchAndConsume(')', parameterStart, true);
|
||||
|
@ -2047,7 +2137,20 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
|
|||
if (params.size() == 1 && !varargAnnotation && !forceFunctionType && !returnTypeIntroducer)
|
||||
{
|
||||
if (allowPack)
|
||||
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, nullptr})};
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, nullptr});
|
||||
if (options.storeCstData)
|
||||
cstNodeMap[node] =
|
||||
allocator.alloc<CstTypePackExplicit>(parameterStart.location.begin, closeArgsLocation.begin, copy(argCommaPositions));
|
||||
return {{}, node};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, nullptr})};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauAstTypeGroup3)
|
||||
|
@ -2058,11 +2161,46 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
|
|||
}
|
||||
|
||||
if (!forceFunctionType && !returnTypeIntroducer && allowPack)
|
||||
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, varargAnnotation})};
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
AstTypePackExplicit* node = allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, varargAnnotation});
|
||||
if (options.storeCstData)
|
||||
cstNodeMap[node] =
|
||||
allocator.alloc<CstTypePackExplicit>(parameterStart.location.begin, closeArgsLocation.begin, copy(argCommaPositions));
|
||||
return {{}, node};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {{}, allocator.alloc<AstTypePackExplicit>(begin.location, AstTypeList{paramTypes, varargAnnotation})};
|
||||
}
|
||||
}
|
||||
|
||||
AstArray<std::optional<AstArgumentName>> paramNames = copy(names);
|
||||
|
||||
return {parseFunctionTypeTail(begin, attributes, generics, genericPacks, paramTypes, paramNames, varargAnnotation), {}};
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
Position returnArrowPosition = lexer.current().location.begin;
|
||||
AstType* node = parseFunctionTypeTail(begin, attributes, generics, genericPacks, paramTypes, paramNames, varargAnnotation);
|
||||
if (options.storeCstData && node->is<AstTypeFunction>())
|
||||
{
|
||||
cstNodeMap[node] = allocator.alloc<CstTypeFunction>(
|
||||
genericsOpenPosition,
|
||||
genericsCommaPositions,
|
||||
genericsClosePosition,
|
||||
parameterStart.location.begin,
|
||||
copy(nameColonPositions),
|
||||
copy(argCommaPositions),
|
||||
closeArgsLocation.begin,
|
||||
returnArrowPosition
|
||||
);
|
||||
}
|
||||
return {node, {}};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {parseFunctionTypeTail(begin, attributes, generics, genericPacks, paramTypes, paramNames, varargAnnotation), {}};
|
||||
}
|
||||
}
|
||||
|
||||
AstType* Parser::parseFunctionTypeTail(
|
||||
|
@ -2124,7 +2262,9 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
|
|||
|
||||
bool isUnion = false;
|
||||
bool isIntersection = false;
|
||||
bool hasOptional = false;
|
||||
// Clip with FFlag::LuauParseOptionalAsNode
|
||||
bool hasOptional_DEPRECATED = false;
|
||||
unsigned int optionalCount = 0;
|
||||
|
||||
Location location = begin;
|
||||
|
||||
|
@ -2148,11 +2288,19 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
|
|||
Location loc = lexer.current().location;
|
||||
nextLexeme();
|
||||
|
||||
if (!hasOptional)
|
||||
parts.push_back(allocator.alloc<AstTypeReference>(loc, std::nullopt, nameNil, std::nullopt, loc));
|
||||
if (FFlag::LuauParseOptionalAsNode)
|
||||
{
|
||||
parts.push_back(allocator.alloc<AstTypeOptional>(Location(loc)));
|
||||
optionalCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hasOptional_DEPRECATED)
|
||||
parts.push_back(allocator.alloc<AstTypeReference>(loc, std::nullopt, nameNil, std::nullopt, loc));
|
||||
}
|
||||
|
||||
isUnion = true;
|
||||
hasOptional = true;
|
||||
hasOptional_DEPRECATED = true;
|
||||
}
|
||||
else if (c == '&')
|
||||
{
|
||||
|
@ -2172,8 +2320,16 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
|
|||
else
|
||||
break;
|
||||
|
||||
if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + hasOptional)
|
||||
ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile");
|
||||
if (FFlag::LuauParseOptionalAsNode)
|
||||
{
|
||||
if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + optionalCount)
|
||||
ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + hasOptional_DEPRECATED)
|
||||
ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile");
|
||||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
|
@ -2465,7 +2621,17 @@ AstTypePack* Parser::parseVariadicArgumentTypePack()
|
|||
|
||||
// This will not fail because of the lookahead guard.
|
||||
expectAndConsume(Lexeme::Dot3, "generic type pack annotation");
|
||||
return allocator.alloc<AstTypePackGeneric>(Location(name.location, end), name.name);
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
AstTypePackGeneric* node = allocator.alloc<AstTypePackGeneric>(Location(name.location, end), name.name);
|
||||
if (options.storeCstData)
|
||||
cstNodeMap[node] = allocator.alloc<CstTypePackGeneric>(end.begin);
|
||||
return node;
|
||||
}
|
||||
else
|
||||
{
|
||||
return allocator.alloc<AstTypePackGeneric>(Location(name.location, end), name.name);
|
||||
}
|
||||
}
|
||||
// Variadic: T
|
||||
else
|
||||
|
@ -2493,7 +2659,17 @@ AstTypePack* Parser::parseTypePack()
|
|||
|
||||
// This will not fail because of the lookahead guard.
|
||||
expectAndConsume(Lexeme::Dot3, "generic type pack annotation");
|
||||
return allocator.alloc<AstTypePackGeneric>(Location(name.location, end), name.name);
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
AstTypePackGeneric* node = allocator.alloc<AstTypePackGeneric>(Location(name.location, end), name.name);
|
||||
if (options.storeCstData)
|
||||
cstNodeMap[node] = allocator.alloc<CstTypePackGeneric>(end.begin);
|
||||
return node;
|
||||
}
|
||||
else
|
||||
{
|
||||
return allocator.alloc<AstTypePackGeneric>(Location(name.location, end), name.name);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: shouldParseTypePack can be removed and parseTypePack can be called unconditionally instead
|
||||
|
@ -3348,12 +3524,13 @@ Parser::Name Parser::parseIndexName(const char* context, const Position& previou
|
|||
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::parseGenericTypeList(
|
||||
bool withDefaultValues,
|
||||
Position* openPosition,
|
||||
TempVector<Position>* commaPositions,
|
||||
AstArray<Position>* commaPositions,
|
||||
Position* closePosition
|
||||
)
|
||||
{
|
||||
TempVector<AstGenericType*> names{scratchGenericTypes};
|
||||
TempVector<AstGenericTypePack*> namePacks{scratchGenericTypePacks};
|
||||
TempVector<Position> localCommaPositions{scratchPosition};
|
||||
|
||||
if (lexer.current().type == '<')
|
||||
{
|
||||
|
@ -3483,7 +3660,7 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
|
|||
if (lexer.current().type == ',')
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||
commaPositions->push_back(lexer.current().location.begin);
|
||||
localCommaPositions.push_back(lexer.current().location.begin);
|
||||
nextLexeme();
|
||||
|
||||
if (lexer.current().type == '>')
|
||||
|
@ -3501,6 +3678,9 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
|
|||
expectMatchAndConsume('>', begin);
|
||||
}
|
||||
|
||||
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||
*commaPositions = copy(localCommaPositions);
|
||||
|
||||
AstArray<AstGenericType*> generics = copy(names);
|
||||
AstArray<AstGenericTypePack*> genericPacks = copy(namePacks);
|
||||
return {generics, genericPacks};
|
||||
|
|
|
@ -125,6 +125,10 @@ static LuauBytecodeType getType(
|
|||
{
|
||||
return getType(group->type, generics, typeAliases, resolveAliases, hostVectorType, userdataTypes, bytecode);
|
||||
}
|
||||
else if (const AstTypeOptional* optional = ty->as<AstTypeOptional>())
|
||||
{
|
||||
return LBC_TYPE_NIL;
|
||||
}
|
||||
|
||||
return LBC_TYPE_ANY;
|
||||
}
|
||||
|
|
|
@ -53,9 +53,9 @@ type A = (number, string) -> ...any
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
CHECK(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "export_alias")
|
||||
|
@ -74,23 +74,23 @@ export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
if (FFlag::LuauStoreCSTData && FFlag::LuauAstTypeGroup3)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0...>( true | any)->(''))");
|
||||
CHECK(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)->(''))");
|
||||
CHECK(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0...>( true | any)->(''))");
|
||||
}
|
||||
else if (FFlag::LuauAstTypeGroup3)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0 ...>(true | any)->(''))");
|
||||
CHECK(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)->(''))");
|
||||
CHECK(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,9 +115,9 @@ end
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk);
|
||||
LUAU_ASSERT(
|
||||
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||
CHECK(module->ats.typeInfo[1].code == Pattern::TypePk);
|
||||
CHECK(
|
||||
module->ats.typeInfo[0].node ==
|
||||
"local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend"
|
||||
);
|
||||
|
@ -145,7 +145,7 @@ end
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
||||
REQUIRE(module->ats.typeInfo.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen_table")
|
||||
|
@ -164,9 +164,9 @@ type Pair<T> = {first: T, second: any}
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = {first: T, second: any}");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
CHECK(module->ats.typeInfo[0].node == "type Pair<T> = {first: T, second: any}");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "assign_uneq")
|
||||
|
@ -190,7 +190,7 @@ local x, y, z = greetings("Dibri") -- mismatch
|
|||
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/B");
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
||||
REQUIRE(module->ats.typeInfo.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen")
|
||||
|
@ -210,9 +210,9 @@ type Pair<T> = (boolean, T) -> ...any
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = (boolean, T)->( ...any)");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
CHECK(module->ats.typeInfo[0].node == "type Pair<T> = (boolean, T)->( ...any)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "typeof_any_in_func")
|
||||
|
@ -234,9 +234,9 @@ TEST_CASE_FIXTURE(ATSFixture, "typeof_any_in_func")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f()\n local a: any = 1\n local b: typeof(a) = 1\n end");
|
||||
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||
CHECK(module->ats.typeInfo[0].node == "local function f()\n local a: any = 1\n local b: typeof(a) = 1\n end");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "generic_types")
|
||||
|
@ -264,9 +264,9 @@ foo(addNumbers)
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncApp);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function foo<A>(a: (...A)->( any),...: A)\n return a(...)\nend");
|
||||
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||
CHECK(module->ats.typeInfo[1].code == Pattern::FuncApp);
|
||||
CHECK(module->ats.typeInfo[0].node == "local function foo<A>(a: (...A)->( any),...: A)\n return a(...)\nend");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "no_annot")
|
||||
|
@ -285,7 +285,7 @@ local character = script.Parent
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
||||
REQUIRE(module->ats.typeInfo.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "if_any")
|
||||
|
@ -314,9 +314,9 @@ end
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
CHECK(
|
||||
module->ats.typeInfo[0].node == "function f(x: any)\nif not x then\nx = {\n y = math.random(0, 2^31-1),\n left = nil,\n right = "
|
||||
"nil\n}\nelse\n local expected = x * 5\nend\nend"
|
||||
);
|
||||
|
@ -342,9 +342,9 @@ TEST_CASE_FIXTURE(ATSFixture, "variadic_any")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(): (number, ...any)\n return 1, 5\n end");
|
||||
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::FuncRet);
|
||||
CHECK(module->ats.typeInfo[0].node == "local function f(): (number, ...any)\n return 1, 5\n end");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection")
|
||||
|
@ -366,9 +366,9 @@ TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT(module->ats.typeInfo[2].code == Pattern::VarAnnot);
|
||||
LUAU_ASSERT(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}");
|
||||
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||
CHECK(module->ats.typeInfo[2].code == Pattern::VarAnnot);
|
||||
CHECK(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_func_arg")
|
||||
|
@ -394,9 +394,9 @@ TEST_CASE_FIXTURE(ATSFixture, "var_func_arg")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 4);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
|
||||
REQUIRE(module->ats.typeInfo.size() == 4);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::VarAny);
|
||||
CHECK(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_func_apps")
|
||||
|
@ -418,9 +418,9 @@ TEST_CASE_FIXTURE(ATSFixture, "var_func_apps")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
|
||||
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::VarAny);
|
||||
CHECK(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
|
||||
}
|
||||
|
||||
|
||||
|
@ -455,7 +455,7 @@ end
|
|||
CHECK_EQ(module->ats.typeInfo[0].node, "descendant.CollisionGroup = CAR_COLLISION_GROUP");
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
||||
REQUIRE(module->ats.typeInfo.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol")
|
||||
|
@ -477,9 +477,9 @@ end
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
||||
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
CHECK(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "racing_3_short")
|
||||
|
@ -518,9 +518,9 @@ initialize()
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 5);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
||||
REQUIRE(module->ats.typeInfo.size() == 5);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
CHECK(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
|
||||
|
@ -596,10 +596,10 @@ initialize()
|
|||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::LuauSkipNoRefineDuringRefinement)
|
||||
CHECK_EQ(module->ats.typeInfo.size(), 12);
|
||||
REQUIRE_EQ(module->ats.typeInfo.size(), 12);
|
||||
else
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
REQUIRE(module->ats.typeInfo.size() == 11);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
{
|
||||
CHECK_EQ(
|
||||
|
@ -612,7 +612,7 @@ initialize()
|
|||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(
|
||||
CHECK(
|
||||
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 "
|
||||
|
@ -685,9 +685,9 @@ initialize()
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 7);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(
|
||||
REQUIRE(module->ats.typeInfo.size() == 7);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
CHECK(
|
||||
module->ats.typeInfo[0].node ==
|
||||
"local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, "
|
||||
"`{kiosk:GetFullName()} has no SpawnLocation part`)\n local promptPart = kiosk:FindFirstChild('Prompt')\n assert(promptPart, "
|
||||
|
@ -719,7 +719,7 @@ TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
||||
REQUIRE(module->ats.typeInfo.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "explicit_pack")
|
||||
|
@ -739,9 +739,9 @@ type Bar = Foo<(number, any)>
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
CHECK(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "local_val")
|
||||
|
@ -760,9 +760,9 @@ local a, b, c = 1 :: any
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Casts);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local a, b, c = 1 :: any");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::Casts);
|
||||
CHECK(module->ats.typeInfo[0].node == "local a, b, c = 1 :: any");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_any_local")
|
||||
|
@ -784,9 +784,9 @@ local x: number, y: any, z, h: nil = 1, nil
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 2, 3");
|
||||
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||
CHECK(module->ats.typeInfo[0].node == "local x: any = 2, 3");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "table_uses_any")
|
||||
|
@ -807,9 +807,9 @@ TEST_CASE_FIXTURE(ATSFixture, "table_uses_any")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 0");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||
CHECK(module->ats.typeInfo[0].node == "local x: any = 0");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "typeof_any")
|
||||
|
@ -830,9 +830,9 @@ TEST_CASE_FIXTURE(ATSFixture, "typeof_any")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end");
|
||||
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||
CHECK(module->ats.typeInfo[1].code == Pattern::FuncArg);
|
||||
CHECK(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned")
|
||||
|
@ -854,9 +854,9 @@ TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Assign);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}");
|
||||
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||
CHECK(module->ats.typeInfo[1].code == Pattern::Assign);
|
||||
CHECK(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret")
|
||||
|
@ -876,9 +876,9 @@ TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some(x: any)\n end");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
CHECK(module->ats.typeInfo[0].node == "function some(x: any)\n end");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret")
|
||||
|
@ -899,9 +899,9 @@ TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::FuncRet);
|
||||
CHECK(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "nested_local")
|
||||
|
@ -923,9 +923,9 @@ TEST_CASE_FIXTURE(ATSFixture, "nested_local")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "function cool(y: number): number\n local g: any = 'gratatataaa'\n return y\n end");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||
CHECK(module->ats.typeInfo[0].node == "function cool(y: number): number\n local g: any = 'gratatataaa'\n return y\n end");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "generic_func")
|
||||
|
@ -946,9 +946,9 @@ TEST_CASE_FIXTURE(ATSFixture, "generic_func")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "function reverse<T>(a: {T}, b: any): {T}\n return a\n end");
|
||||
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||
CHECK(module->ats.typeInfo[0].node == "function reverse<T>(a: {T}, b: any): {T}\n return a\n end");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "type_alias_any")
|
||||
|
@ -968,9 +968,9 @@ TEST_CASE_FIXTURE(ATSFixture, "type_alias_any")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
|
||||
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
CHECK(module->ats.typeInfo[0].node == "type Clear = any");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "multi_module_any")
|
||||
|
@ -1001,9 +1001,9 @@ TEST_CASE_FIXTURE(ATSFixture, "multi_module_any")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/B");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
|
||||
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||
CHECK(module->ats.typeInfo[0].node == "type Clear = any");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "cast_on_cyclic_req")
|
||||
|
@ -1029,9 +1029,9 @@ TEST_CASE_FIXTURE(ATSFixture, "cast_on_cyclic_req")
|
|||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/B");
|
||||
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Alias);
|
||||
LUAU_ASSERT(module->ats.typeInfo[1].node == "type Clear = any");
|
||||
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||
CHECK(module->ats.typeInfo[1].code == Pattern::Alias);
|
||||
CHECK(module->ats.typeInfo[1].node == "type Clear = any");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include "Luau/VisitType.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
|
||||
#include "ClassFixture.h"
|
||||
#include "Fixture.h"
|
||||
#include "ScopedFlags.h"
|
||||
|
||||
|
@ -19,6 +21,8 @@ LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
|||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
|
||||
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
|
||||
LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -154,6 +158,10 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
|
|||
{
|
||||
};
|
||||
|
||||
struct ACClassFixture : ACFixtureImpl<ClassFixture>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("AutocompleteTest");
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "empty_program")
|
||||
|
@ -4416,4 +4424,76 @@ local x = 1 + result.
|
|||
CHECK(ac.entryMap.count("x"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACClassFixture, "ac_dont_overflow_on_recursive_union")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauAutocompleteUnionCopyPreviousSeen, true};
|
||||
check(R"(
|
||||
local table1: {ChildClass} = {}
|
||||
local table2 = {}
|
||||
|
||||
for index, value in table2[1] do
|
||||
table.insert(table1, value)
|
||||
value.@1
|
||||
end
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
// RIDE-11517: This should *really* be the members of `ChildClass`, but
|
||||
// would previously stack overflow.
|
||||
CHECK(ac.entryMap.empty());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_has_types_definitions")
|
||||
{
|
||||
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
|
||||
if (!FFlag::LuauUserTypeFunTypecheck)
|
||||
return;
|
||||
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
check(R"(
|
||||
type function foo()
|
||||
types.@1
|
||||
end
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
CHECK_EQ(ac.entryMap.count("singleton"), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_private_scope")
|
||||
{
|
||||
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
|
||||
if (!FFlag::LuauUserTypeFunTypecheck)
|
||||
return;
|
||||
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
// Global scope polution by the embedder has no effect
|
||||
addGlobalBinding(frontend.globals, "thisAlsoShouldNotBeThere", Binding{builtinTypes->anyType});
|
||||
addGlobalBinding(frontend.globalsForAutocomplete, "thisAlsoShouldNotBeThere", Binding{builtinTypes->anyType});
|
||||
|
||||
check(R"(
|
||||
local function thisShouldNotBeThere() end
|
||||
|
||||
type function thisShouldBeThere() end
|
||||
|
||||
type function foo()
|
||||
this@1
|
||||
end
|
||||
|
||||
this@2
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
CHECK_EQ(ac.entryMap.count("thisShouldNotBeThere"), 0);
|
||||
CHECK_EQ(ac.entryMap.count("thisAlsoShouldNotBeThere"), 0);
|
||||
CHECK_EQ(ac.entryMap.count("thisShouldBeThere"), 1);
|
||||
|
||||
ac = autocomplete('2');
|
||||
CHECK_EQ(ac.entryMap.count("thisShouldNotBeThere"), 1);
|
||||
CHECK_EQ(ac.entryMap.count("thisAlsoShouldNotBeThere"), 1);
|
||||
CHECK_EQ(ac.entryMap.count("thisShouldBeThere"), 0);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -9,7 +9,8 @@ using std::nullopt;
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
ClassFixture::ClassFixture()
|
||||
ClassFixture::ClassFixture(bool prepareAutocomplete)
|
||||
: BuiltinsFixture(prepareAutocomplete)
|
||||
{
|
||||
GlobalTypes& globals = frontend.globals;
|
||||
TypeArena& arena = globals.globalTypes;
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Luau
|
|||
|
||||
struct ClassFixture : BuiltinsFixture
|
||||
{
|
||||
ClassFixture();
|
||||
explicit ClassFixture(bool prepareAutocomplete = false);
|
||||
|
||||
TypeId vector2Type;
|
||||
TypeId vector2InstanceType;
|
||||
|
|
|
@ -34,6 +34,7 @@ void ConstraintGeneratorFixture::generateConstraints(const std::string& code)
|
|||
builtinTypes,
|
||||
NotNull(&ice),
|
||||
frontend.globals.globalScope,
|
||||
frontend.globals.globalTypeFunctionScope,
|
||||
/*prepareModuleScope*/ nullptr,
|
||||
&logger,
|
||||
NotNull{dfg.get()},
|
||||
|
|
|
@ -323,6 +323,7 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
|
|||
NotNull{&moduleResolver},
|
||||
NotNull{&fileResolver},
|
||||
frontend.globals.globalScope,
|
||||
frontend.globals.globalTypeFunctionScope,
|
||||
/*prepareModuleScope*/ nullptr,
|
||||
frontend.options,
|
||||
{},
|
||||
|
|
|
@ -43,6 +43,8 @@ LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
|
|||
LUAU_FASTFLAG(LuauCloneTypeAliasBindings)
|
||||
LUAU_FASTFLAG(LuauDoNotClonePersistentBindings)
|
||||
LUAU_FASTFLAG(LuauCloneReturnTypePack)
|
||||
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
|
||||
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
|
||||
{
|
||||
|
@ -83,6 +85,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
ScopedFastFlag luauCloneTypeAliasBindings{FFlag::LuauCloneTypeAliasBindings, true};
|
||||
ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true};
|
||||
ScopedFastFlag luauCloneReturnTypePack{FFlag::LuauCloneReturnTypePack, true};
|
||||
ScopedFastFlag luauIncrementalAutocompleteDemandBasedCloning{FFlag::LuauIncrementalAutocompleteDemandBasedCloning, true};
|
||||
|
||||
FragmentAutocompleteFixtureImpl()
|
||||
: BaseType(true)
|
||||
|
@ -157,12 +160,14 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
this->check(document, getOptions());
|
||||
|
||||
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
|
||||
assertions(result);
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, false};
|
||||
this->check(document, getOptions());
|
||||
|
||||
result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
|
||||
assertions(result);
|
||||
}
|
||||
|
||||
|
@ -2341,4 +2346,40 @@ z = a.P.E
|
|||
autocompleteFragmentInBothSolvers(source, dest, Position{8, 9}, [](FragmentAutocompleteStatusResult& _) {});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "user_defined_type_function_local")
|
||||
{
|
||||
ScopedFastFlag luauUserTypeFunTypecheck{FFlag::LuauUserTypeFunTypecheck, true};
|
||||
|
||||
const std::string source = R"(--!strict
|
||||
type function foo(x: type): type
|
||||
if x.tag == "singleton" then
|
||||
local t = x:value()
|
||||
|
||||
return types.unionof(types.singleton(t), types.singleton(nil))
|
||||
end
|
||||
|
||||
return types.number
|
||||
end
|
||||
)";
|
||||
|
||||
const std::string dest = R"(--!strict
|
||||
type function foo(x: type): type
|
||||
if x.tag == "singleton" then
|
||||
local t = x:value()
|
||||
x
|
||||
return types.unionof(types.singleton(t), types.singleton(nil))
|
||||
end
|
||||
|
||||
return types.number
|
||||
end
|
||||
)";
|
||||
|
||||
// Only checking in new solver as old solver doesn't handle type functions and constraint solver will ICE
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
this->check(source, getOptions());
|
||||
|
||||
FragmentAutocompleteStatusResult result = autocompleteFragment(dest, Position{4, 9}, std::nullopt);
|
||||
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -18,6 +18,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
|
|||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
|
||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -920,7 +921,17 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
|
|||
// When this test fails, it is because the TypeIds needed by the error have been deallocated.
|
||||
// It is thus basically impossible to predict what will happen when this assert is evaluated.
|
||||
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
REQUIRE_EQ(
|
||||
"Type\n\t"
|
||||
"'{ count: string }'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'{ Count: number }'",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
REQUIRE_EQ(
|
||||
R"(Type
|
||||
'{ count: string }'
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LexerFixInterpStringStart)
|
||||
|
||||
TEST_SUITE_BEGIN("LexerTests");
|
||||
|
||||
TEST_CASE("broken_string_works")
|
||||
|
@ -156,7 +154,7 @@ TEST_CASE("string_interpolation_basic")
|
|||
Lexeme interpEnd = lexer.next();
|
||||
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
|
||||
// The InterpStringEnd should start with }, not `.
|
||||
CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12);
|
||||
CHECK_EQ(interpEnd.location.begin.column, 11);
|
||||
}
|
||||
|
||||
TEST_CASE("string_interpolation_full")
|
||||
|
@ -177,7 +175,7 @@ TEST_CASE("string_interpolation_full")
|
|||
Lexeme interpMid = lexer.next();
|
||||
CHECK_EQ(interpMid.type, Lexeme::InterpStringMid);
|
||||
CHECK_EQ(interpMid.toString(), "} {");
|
||||
CHECK_EQ(interpMid.location.begin.column, FFlag::LexerFixInterpStringStart ? 11 : 12);
|
||||
CHECK_EQ(interpMid.location.begin.column, 11);
|
||||
|
||||
Lexeme quote2 = lexer.next();
|
||||
CHECK_EQ(quote2.type, Lexeme::QuotedString);
|
||||
|
@ -186,7 +184,7 @@ TEST_CASE("string_interpolation_full")
|
|||
Lexeme interpEnd = lexer.next();
|
||||
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
|
||||
CHECK_EQ(interpEnd.toString(), "} end`");
|
||||
CHECK_EQ(interpEnd.location.begin.column, FFlag::LexerFixInterpStringStart ? 19 : 20);
|
||||
CHECK_EQ(interpEnd.location.begin.column, 19);
|
||||
}
|
||||
|
||||
TEST_CASE("string_interpolation_double_brace")
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
|
||||
LUAU_FASTFLAG(LuauNonStrictVisitorImprovements)
|
||||
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix)
|
||||
LUAU_FASTFLAG(LuauNormalizedBufferIsNotUnknown)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -359,6 +361,23 @@ end
|
|||
NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors_2")
|
||||
{
|
||||
ScopedFastFlag luauNonStrictFuncDefErrorFix{FFlag::LuauNonStrictFuncDefErrorFix, true};
|
||||
ScopedFastFlag luauNonStrictVisitorImprovements{FFlag::LuauNonStrictVisitorImprovements, true};
|
||||
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local t = {function(x)
|
||||
abs(x)
|
||||
lower(x)
|
||||
end}
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result);
|
||||
NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result);
|
||||
CHECK(toString(result.errors[2]) == "Argument x with type 'unknown' is used in a way that will run time error");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_fn_produces_error")
|
||||
{
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
|
@ -649,4 +668,17 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict")
|
|||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "buffer_is_not_unknown")
|
||||
{
|
||||
ScopedFastFlag luauNormalizedBufferIsNotUnknown{FFlag::LuauNormalizedBufferIsNotUnknown, true};
|
||||
|
||||
CheckResult result = check(Mode::Nonstrict, R"(
|
||||
local function wrap(b: buffer, i: number, v: number)
|
||||
buffer.writeu32(b, i * 4, v)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauFixNormalizedIntersectionOfNegatedClass)
|
||||
LUAU_FASTFLAG(LuauNormalizeNegationFix)
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -852,7 +851,6 @@ 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>")));
|
||||
|
|
|
@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
|||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -3818,7 +3819,10 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
|
|||
}
|
||||
else
|
||||
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
|
||||
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
|
||||
if (FFlag::LuauParseOptionalAsNode)
|
||||
CHECK(unionTy->types.data[1]->is<AstTypeOptional>()); // ?
|
||||
else
|
||||
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty")
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauExtendedSimpleRequire)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
|
@ -182,8 +180,6 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_string_indexexpr")
|
|||
|
||||
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_group")
|
||||
{
|
||||
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
|
||||
|
||||
AstStatBlock* block = parse(R"(
|
||||
local R = (((game).Test))
|
||||
require(R)
|
||||
|
@ -200,8 +196,6 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_group")
|
|||
|
||||
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation")
|
||||
{
|
||||
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
|
||||
|
||||
AstStatBlock* block = parse(R"(
|
||||
local R = game.Test :: (typeof(game.Redirect))
|
||||
require(R)
|
||||
|
@ -218,8 +212,6 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation")
|
|||
|
||||
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation_2")
|
||||
{
|
||||
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
|
||||
|
||||
AstStatBlock* block = parse(R"(
|
||||
local R = game.Test :: (typeof(game.Redirect))
|
||||
local N = R.Nested
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
|
||||
#include <initializer_list>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauNormalizedBufferIsNotUnknown)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -961,6 +962,20 @@ TEST_IS_NOT_SUBTYPE(childClass, negate(rootClass));
|
|||
TEST_IS_NOT_SUBTYPE(childClass, meet(builtinTypes->classType, negate(rootClass)));
|
||||
TEST_IS_SUBTYPE(anotherChildClass, meet(builtinTypes->classType, negate(childClass)));
|
||||
|
||||
// Negated primitives against unknown
|
||||
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->booleanType));
|
||||
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->numberType));
|
||||
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->stringType));
|
||||
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->threadType));
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "unknown <!: ~buffer")
|
||||
{
|
||||
// TODO: replace with TEST_IS_NOT_SUBTYPE on flag removal
|
||||
ScopedFastFlag luauNormalizedBufferIsNotUnknown{FFlag::LuauNormalizedBufferIsNotUnknown, true};
|
||||
|
||||
CHECK_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->bufferType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType);
|
||||
|
|
|
@ -5,14 +5,16 @@
|
|||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "ScopedFlags.h"
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
||||
|
@ -871,9 +873,28 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
|
|||
)");
|
||||
|
||||
std::string expected;
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
expected =
|
||||
"Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; \n"
|
||||
"this is because in the 1st entry in the type pack, accessing `c.d` results in `string` in the former type and `number` in the latter "
|
||||
"type, and `string` is not exactly `number`";
|
||||
else if (FFlag::LuauSolverV2)
|
||||
expected =
|
||||
R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)";
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
expected = R"(Type
|
||||
'{ a: number, b: string, c: { d: string } }'
|
||||
could not be converted into
|
||||
'{| a: number, b: string, c: {| d: number |} |}'
|
||||
caused by:
|
||||
Property 'c' is not compatible.
|
||||
Type
|
||||
'{ d: string }'
|
||||
could not be converted into
|
||||
'{| d: number |}'
|
||||
caused by:
|
||||
Property 'd' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
else
|
||||
expected = R"(Type
|
||||
'{ a: number, b: string, c: { d: string } }'
|
||||
|
|
|
@ -15,7 +15,6 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3);
|
||||
LUAU_FASTFLAG(LexerFixInterpStringStart)
|
||||
|
||||
TEST_SUITE_BEGIN("TranspilerTests");
|
||||
|
||||
|
@ -304,6 +303,95 @@ TEST_CASE("function")
|
|||
CHECK_EQ(two, transpile(two).code);
|
||||
}
|
||||
|
||||
TEST_CASE("function_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
const std::string two = R"( function p(o, m, ...) end )";
|
||||
CHECK_EQ(two, transpile(two).code);
|
||||
|
||||
const std::string three = R"( function p( o, m, ...) end )";
|
||||
CHECK_EQ(three, transpile(three).code);
|
||||
|
||||
const std::string four = R"( function p(o , m, ...) end )";
|
||||
CHECK_EQ(four, transpile(four).code);
|
||||
|
||||
const std::string five = R"( function p(o, m, ...) end )";
|
||||
CHECK_EQ(five, transpile(five).code);
|
||||
|
||||
const std::string six = R"( function p(o, m , ...) end )";
|
||||
CHECK_EQ(six, transpile(six).code);
|
||||
|
||||
const std::string seven = R"( function p(o, m, ...) end )";
|
||||
CHECK_EQ(seven, transpile(seven).code);
|
||||
|
||||
const std::string eight = R"( function p(o, m, ... ) end )";
|
||||
CHECK_EQ(eight, transpile(eight).code);
|
||||
|
||||
const std::string nine = R"( function p(o, m, ...) end )";
|
||||
CHECK_EQ(nine, transpile(nine).code);
|
||||
}
|
||||
|
||||
TEST_CASE("function_with_types_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p <X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X , Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z ...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z... >(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...> (o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
// TODO(CLI-139347): re-enable test once colon positions are supported
|
||||
// code = R"( function p<X, Y, Z...>(o : string, m: number, ...: any): string end )";
|
||||
// CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string , m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
// TODO(CLI-139347): re-enable test once colon positions are supported
|
||||
// code = R"( function p<X, Y, Z...>(o: string, m: number, ... : any): string end )";
|
||||
// CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any ): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
// TODO(CLI-139347): re-enable test once return type positions are supported
|
||||
// code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any) :string end )";
|
||||
// CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE("returns_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
|
@ -968,7 +1056,17 @@ TEST_CASE_FIXTURE(Fixture, "type_lists_should_be_emitted_correctly")
|
|||
end
|
||||
)";
|
||||
|
||||
std::string expected = R"(
|
||||
std::string expected = FFlag::LuauStoreCSTData ? R"(
|
||||
local a:(a:string,b:number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number)
|
||||
end
|
||||
|
||||
local b:(...string)->(...number)=function(...:string): ...number
|
||||
end
|
||||
|
||||
local c:()->()=function(): ()
|
||||
end
|
||||
)"
|
||||
: R"(
|
||||
local a:(string,number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number)
|
||||
end
|
||||
|
||||
|
@ -1157,6 +1255,49 @@ local b: Packed<(number, string)>
|
|||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_packs_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
std::string code = R"( type _ = Packed< T...> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed<T ...> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed< ...T> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed<... T> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed< ()> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed< (string, number)> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed<( string, number)> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed<(string , number)> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed<(string, number)> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed<(string, number )> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed<(string, number) > )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed<( )> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type _ = Packed<() > )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested")
|
||||
{
|
||||
std::string code = "local a: ((number)->(string))|((string)->(string))";
|
||||
|
@ -1488,7 +1629,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
|
|||
{
|
||||
ScopedFastFlag fflags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LexerFixInterpStringStart, true},
|
||||
};
|
||||
std::string code = R"( local _ = `hello {name}` )";
|
||||
|
||||
|
@ -1499,7 +1639,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
|
|||
{
|
||||
ScopedFastFlag fflags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LexerFixInterpStringStart, true},
|
||||
};
|
||||
std::string code = R"( local _ = `hello {
|
||||
name
|
||||
|
@ -1512,7 +1651,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
|
|||
{
|
||||
ScopedFastFlag fflags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LexerFixInterpStringStart, true},
|
||||
};
|
||||
std::string code = R"(
|
||||
error(
|
||||
|
@ -1536,7 +1674,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape")
|
|||
{
|
||||
ScopedFastFlag fflags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LexerFixInterpStringStart, true},
|
||||
};
|
||||
std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )";
|
||||
|
||||
|
@ -1550,6 +1687,22 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions")
|
|||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
std::string code = R"( type function foo() end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type function foo() end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type function foo () end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"( export type function foo() end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
|
@ -1752,4 +1905,122 @@ TEST_CASE("transpile_types_preserve_parentheses_style")
|
|||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE("fuzzer_transpile_with_zero_location")
|
||||
{
|
||||
const std::string example = R"(
|
||||
if _ then
|
||||
elseif _ then
|
||||
elseif l0 then
|
||||
else
|
||||
local function l0<t0>(...):(t0<t0...>,(any)|(<t0>((any)|(<t0>(""[[[[[[[[[[[[[[[[[[[[[[[[!*t")->()))->()))
|
||||
end
|
||||
end
|
||||
)";
|
||||
|
||||
Luau::ParseOptions parseOptions;
|
||||
parseOptions.captureComments = true;
|
||||
|
||||
auto allocator = std::make_unique<Luau::Allocator>();
|
||||
auto names = std::make_unique<Luau::AstNameTable>(*allocator);
|
||||
ParseResult parseResult = Parser::parse(example.data(), example.size(), *names, *allocator, parseOptions);
|
||||
|
||||
transpileWithTypes(*parseResult.root);
|
||||
}
|
||||
|
||||
TEST_CASE("transpile_type_function_unnamed_arguments")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
std::string code = R"( type Foo = () -> () )";
|
||||
CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = () -> () )";
|
||||
CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (string) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (string) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (string, number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (string, number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = ( string, number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = ( string, number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (string , number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (string , number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (string, number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (string, number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (string, number ) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (string, number ) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (string, number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (string, number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (string, number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (string, number) ->() )", transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE("transpile_type_function_named_arguments")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
std::string code = R"( type Foo = (x: string) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (x: string) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (x: string, y: number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (x: string, y: number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = ( x: string, y: number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = ( x: string, y: number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (x : string, y: number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (x : string, y: number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (x: string, y: number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (x: string, y: number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (x: string, y: number) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (x: string, y: number) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (number, info: string) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (number, info: string) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = (first: string, second: string, ...string) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (first: string, second: string, ...string) ->() )", transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE("transpile_type_function_generics")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
std::string code = R"( type Foo = <X, Y, Z...>() -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X, Y, Z...>() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = <X, Y, Z...>() -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X, Y, Z...>() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = < X, Y, Z...>() -> () )";
|
||||
CHECK_EQ(R"( type Foo = < X, Y, Z...>() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = <X , Y, Z...>() -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X , Y, Z...>() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = <X, Y, Z...>() -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X, Y, Z...>() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = <X, Y , Z...>() -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X, Y , Z...>() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = <X, Y, Z...>() -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X, Y, Z...>() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = <X, Y, Z ...>() -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X, Y, Z ...>() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = <X, Y, Z... >() -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X, Y, Z... >() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
code = R"( type Foo = <X, Y, Z...> () -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X, Y, Z...> () ->() )", transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauIndexTypeFunctionImprovements)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||
LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods)
|
||||
LUAU_FASTFLAG(LuauMetatableTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauIndexAnyIsAny)
|
||||
|
||||
struct TypeFunctionFixture : Fixture
|
||||
{
|
||||
|
@ -904,6 +907,21 @@ end
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_of_any_is_any")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sff{FFlag::LuauIndexAnyIsAny, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = index<any, "a">
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireTypeAlias("T")) == "any");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
|
@ -965,6 +983,31 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_array")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_metatable_should_not_crash_index")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sff{FFlag::LuauIndexTypeFunctionImprovements, true};
|
||||
|
||||
// t :: t1 where t1 = {metatable {__index: t1, __tostring: (t1) -> string}}
|
||||
CheckResult result = check(R"(
|
||||
local mt = {}
|
||||
local t = setmetatable({}, mt)
|
||||
mt.__index = t
|
||||
|
||||
function mt:__tostring()
|
||||
return t.p
|
||||
end
|
||||
|
||||
type IndexFromT = index<typeof(t), "p">
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ("Type 't' does not have key 'p'", toString(result.errors[0]));
|
||||
CHECK_EQ("Property '\"p\"' does not exist on type 't'", toString(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_generic_types")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
|
@ -1003,6 +1046,61 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_bad_indexer")
|
|||
CHECK(toString(result.errors[1]) == "Property 'boolean' does not exist on type 'MyObject'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_on_function_metamethods")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sff[]
|
||||
{
|
||||
{FFlag::LuauIndexTypeFunctionFunctionMetamethods, true},
|
||||
{FFlag::LuauIndexTypeFunctionImprovements, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Foo = {x: string}
|
||||
local t = {}
|
||||
setmetatable(t, {
|
||||
__index = function(x: string): Foo
|
||||
return {x = x}
|
||||
end
|
||||
})
|
||||
|
||||
type Bar = index<typeof(t), "bar">
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
|
||||
CHECK_EQ(toString(requireTypeAlias("Bar"), {true}), "{ x: string }");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_on_function_metamethods2")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sff[]
|
||||
{
|
||||
{FFlag::LuauIndexTypeFunctionFunctionMetamethods, true},
|
||||
{FFlag::LuauIndexTypeFunctionImprovements, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Foo = {x: string}
|
||||
local t = {}
|
||||
setmetatable(t, {
|
||||
__index = function(x: string): Foo
|
||||
return {x = x}
|
||||
end
|
||||
})
|
||||
|
||||
type Bar = index<typeof(t), number>
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_var_indexer")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
|
|
|
@ -7,13 +7,12 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauTypeFunSingletonEquality)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType)
|
||||
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
|
||||
LUAU_FASTFLAG(LuauTypeFunPrintFix)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
|
||||
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
|
||||
|
||||
|
@ -613,9 +612,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work")
|
|||
ty:setreturns(nil, types.boolean) -- (string, number) -> (...boolean)
|
||||
if ty:is("function") then
|
||||
-- creating a copy of `ty` parameters
|
||||
local arr = {}
|
||||
for index, val in ty:parameters().head do
|
||||
table.insert(arr, val)
|
||||
local arr: {type} = {}
|
||||
local args = ty:parameters().head
|
||||
if args then
|
||||
for index, val in args do
|
||||
table.insert(arr, val)
|
||||
end
|
||||
end
|
||||
return types.newfunction({head = arr}, ty:returns()) -- (string, number) -> (...boolean)
|
||||
end
|
||||
|
@ -648,7 +650,6 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works")
|
|||
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works2")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag luauTypeFunFixHydratedClasses{FFlag::LuauTypeFunFixHydratedClasses, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function serialize_class(arg)
|
||||
|
@ -719,7 +720,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability")
|
|||
readresult = types.boolean,
|
||||
writeresult = types.boolean,
|
||||
}
|
||||
local ty = types.newtable(props, indexer, nil) -- {[number]: boolean}
|
||||
local ty = types.newtable(nil, indexer, nil) -- {[number]: boolean}
|
||||
ty:setproperty(types.singleton("string"), types.number) -- {string: number, [number]: boolean}
|
||||
local metatbl = types.newtable(nil, nil, ty) -- { { }, @metatable { [number]: boolean, string: number } }
|
||||
-- mutate the table
|
||||
|
@ -894,6 +895,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_eq_metamethod")
|
|||
if p1 == p2 and t1 == t2 then
|
||||
return types.number
|
||||
end
|
||||
return types.unknown
|
||||
end
|
||||
local function ok(idx: hello<>): number return idx end
|
||||
)");
|
||||
|
@ -988,8 +990,37 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_3")
|
|||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
CHECK(toString(result.errors[0]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)");
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(5, result);
|
||||
CHECK(toString(result.errors[0]) == R"(Unknown global 'fourth')");
|
||||
CHECK(toString(result.errors[1]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
CHECK(toString(result.errors[0]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_unordered")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function bar()
|
||||
return types.singleton(foo())
|
||||
end
|
||||
type function foo()
|
||||
return "hi"
|
||||
end
|
||||
local function ok(idx: bar<>): nil return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
||||
REQUIRE(tpm);
|
||||
CHECK(toString(tpm->givenTp) == "\"hi\"");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state")
|
||||
|
@ -1013,10 +1044,21 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state")
|
|||
local function ok2(idx: bar<'y'>): nil return idx end
|
||||
)");
|
||||
|
||||
// We are only checking first errors, others are mostly duplicates
|
||||
LUAU_REQUIRE_ERROR_COUNT(8, result);
|
||||
CHECK(toString(result.errors[0]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)");
|
||||
CHECK(toString(result.errors[1]) == R"(Type function instance bar<"x"> is uninhabited)");
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
// We are only checking first errors, others are mostly duplicates
|
||||
LUAU_REQUIRE_ERROR_COUNT(9, result);
|
||||
CHECK(toString(result.errors[0]) == R"(Unknown global 'glob')");
|
||||
CHECK(toString(result.errors[1]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)");
|
||||
CHECK(toString(result.errors[2]) == R"(Type function instance bar<"x"> is uninhabited)");
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are only checking first errors, others are mostly duplicates
|
||||
LUAU_REQUIRE_ERROR_COUNT(8, result);
|
||||
CHECK(toString(result.errors[0]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)");
|
||||
CHECK(toString(result.errors[1]) == R"(Type function instance bar<"x"> is uninhabited)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_math_reset")
|
||||
|
@ -1075,10 +1117,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global")
|
|||
local function ok(idx: illegal<number>): nil return idx end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result); // There are 2 type function uninhabited error, 2 user defined type function error
|
||||
UserDefinedTypeFunctionError* e = get<UserDefinedTypeFunctionError>(result.errors[0]);
|
||||
REQUIRE(e);
|
||||
CHECK(e->message == "'illegal' type function errored at runtime: [string \"illegal\"]:3: this function is not supported in type functions");
|
||||
if (FFlag::LuauUserTypeFunTypecheck)
|
||||
{
|
||||
// We are only checking first errors, others are mostly duplicates
|
||||
LUAU_REQUIRE_ERROR_COUNT(5, result);
|
||||
CHECK(toString(result.errors[0]) == R"(Unknown global 'gcinfo')");
|
||||
CHECK(
|
||||
toString(result.errors[1]) ==
|
||||
R"('illegal' type function errored at runtime: [string "illegal"]:3: this function is not supported in type functions)"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result); // There are 2 type function uninhabited error, 2 user defined type function error
|
||||
UserDefinedTypeFunctionError* e = get<UserDefinedTypeFunctionError>(result.errors[0]);
|
||||
REQUIRE(e);
|
||||
CHECK(e->message == "'illegal' type function errored at runtime: [string \"illegal\"]:3: this function is not supported in type functions");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc")
|
||||
|
@ -1172,7 +1227,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "no_type_methods_on_types")
|
|||
|
||||
CheckResult result = check(R"(
|
||||
type function test(x)
|
||||
return if types.is(x, "number") then types.string else types.boolean
|
||||
return if (types :: any).is(x, "number") then types.string else types.boolean
|
||||
end
|
||||
local function ok(tbl: test<number>): never return tbl end
|
||||
)");
|
||||
|
@ -1243,9 +1298,36 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tag_field")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
CHECK(toString(result.errors[0]) == R"(Type pack '"number"' could not be converted into 'never'; at [0], "number" is not a subtype of never)");
|
||||
CHECK(toString(result.errors[1]) == R"(Type pack '"string"' could not be converted into 'never'; at [0], "string" is not a subtype of never)");
|
||||
CHECK(toString(result.errors[2]) == R"(Type pack '"table"' could not be converted into 'never'; at [0], "table" is not a subtype of never)");
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
|
||||
CHECK(
|
||||
toString(result.errors[0]) ==
|
||||
"Type pack '\"number\"' could not be converted into 'never'; \n"
|
||||
R"(this is because the 1st entry in the type pack is `"number"` in the former type and `never` in the latter type, and `"number"` is not a subtype of `never`)"
|
||||
);
|
||||
CHECK(
|
||||
toString(result.errors[1]) ==
|
||||
"Type pack '\"string\"' could not be converted into 'never'; \n"
|
||||
R"(this is because the 1st entry in the type pack is `"string"` in the former type and `never` in the latter type, and `"string"` is not a subtype of `never`)"
|
||||
);
|
||||
CHECK(
|
||||
toString(result.errors[2]) ==
|
||||
"Type pack '\"table\"' could not be converted into 'never'; \n"
|
||||
R"(this is because the 1st entry in the type pack is `"table"` in the former type and `never` in the latter type, and `"table"` is not a subtype of `never`)"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(
|
||||
toString(result.errors[0]) == R"(Type pack '"number"' could not be converted into 'never'; at [0], "number" is not a subtype of never)"
|
||||
);
|
||||
CHECK(
|
||||
toString(result.errors[1]) == R"(Type pack '"string"' could not be converted into 'never'; at [0], "string" is not a subtype of never)"
|
||||
);
|
||||
CHECK(toString(result.errors[2]) == R"(Type pack '"table"' could not be converted into 'never'; at [0], "table" is not a subtype of never)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_serialization")
|
||||
|
@ -1293,8 +1375,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "implicit_export")
|
|||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
type function concat(a, b)
|
||||
return types.singleton(a:value() .. b:value())
|
||||
type function concat(a: type, b: type)
|
||||
local as = a:value()
|
||||
local bs = b:value()
|
||||
assert(typeof(as) == "string")
|
||||
assert(typeof(bs) == "string")
|
||||
return types.singleton(as .. bs)
|
||||
end
|
||||
export type Concat<T, U> = concat<T, U>
|
||||
local a: concat<'first', 'second'>
|
||||
|
@ -1342,8 +1428,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "explicit_export")
|
|||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type function concat(a, b)
|
||||
return types.singleton(a:value() .. b:value())
|
||||
export type function concat(a: type, b: type)
|
||||
local as = a:value()
|
||||
local bs = b:value()
|
||||
assert(typeof(as) == "string")
|
||||
assert(typeof(bs) == "string")
|
||||
return types.singleton(as .. bs)
|
||||
end
|
||||
local a: concat<'first', 'second'>
|
||||
return {}
|
||||
|
@ -1892,7 +1982,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque")
|
|||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function compare(arg)
|
||||
|
@ -1909,7 +1998,6 @@ local function ok(idx: compare<true>): false return idx end
|
|||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function compare(arg)
|
||||
|
@ -1926,7 +2014,6 @@ local function ok(idx: compare<"a">): false return idx end
|
|||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeof_type_userdata_returns_type")
|
||||
{
|
||||
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag luauUserTypeFunTypeofReturnsType{FFlag::LuauUserTypeFunTypeofReturnsType, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function test(t)
|
||||
|
@ -1982,4 +2069,38 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_parent_ops")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_success")
|
||||
{
|
||||
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
|
||||
if (!FFlag::LuauUserTypeFunTypecheck)
|
||||
return;
|
||||
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foo(x: type)
|
||||
return types.singleton(x.tag)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_failure")
|
||||
{
|
||||
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
|
||||
if (!FFlag::LuauUserTypeFunTypecheck)
|
||||
return;
|
||||
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foo()
|
||||
return types.singleton({1})
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -11,6 +11,7 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
|
@ -216,7 +217,11 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type '{ v: string }' could not be converted into 'T<number>'; at [read "v"], string is not exactly number)";
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
|
||||
? "Type '{ v: string }' could not be converted into 'T<number>'; \n"
|
||||
"this is because accessing `v` results in `string` in the former type and `number` in the latter type, and "
|
||||
"`string` is not exactly `number`"
|
||||
: R"(Type '{ v: string }' could not be converted into 'T<number>'; at [read "v"], string is not exactly number)";
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
@ -236,7 +241,11 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected =
|
||||
R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
|
||||
(FFlag::LuauImproveTypePathsInErrors)
|
||||
? "Type '{ t: { v: string } }' could not be converted into 'U<number>'; \n"
|
||||
"this is because accessing `t.v` results in `string` in the former type and `number` in the latter type, and `string` is not exactly "
|
||||
"`number`"
|
||||
: R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
|
||||
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
|
|
|
@ -12,7 +12,7 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
|
||||
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("BuiltinTests");
|
||||
|
||||
|
@ -146,7 +146,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
|
||||
"'(number, number) -> boolean'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'((string, string) -> boolean)?'"
|
||||
"\ncaused by:\n"
|
||||
" None of the union options are compatible. For example:\n"
|
||||
"Type\n\t"
|
||||
"'(number, number) -> boolean'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(string, string) -> boolean'"
|
||||
"\ncaused by:\n"
|
||||
" Argument #1 type is not compatible.\n"
|
||||
"Type 'string' could not be converted into 'number'"
|
||||
: R"(Type
|
||||
'(number, number) -> boolean'
|
||||
could not be converted into
|
||||
'((string, string) -> boolean)?'
|
||||
|
@ -985,7 +998,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
CHECK_EQ(
|
||||
"Type 'number?' could not be converted into 'number'; \n"
|
||||
"this is because the 2nd component of the union is `nil`, which is not a subtype of `number`",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ(
|
||||
"Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)",
|
||||
toString(result.errors[0])
|
||||
|
@ -1256,8 +1276,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_errors_on_non_tables")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_persistent_skip")
|
||||
{
|
||||
ScopedFastFlag luauFreezeIgnorePersistent{FFlag::LuauFreezeIgnorePersistent, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
table.freeze(table)
|
||||
)");
|
||||
|
@ -1267,8 +1285,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_persistent_skip")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_clone_persistent_skip")
|
||||
{
|
||||
ScopedFastFlag luauFreezeIgnorePersistent{FFlag::LuauFreezeIgnorePersistent, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
table.clone(table)
|
||||
)");
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
using namespace Luau;
|
||||
using std::nullopt;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferClasses");
|
||||
|
||||
|
@ -545,7 +546,13 @@ local b: B = a
|
|||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
CHECK(
|
||||
"Type 'A' could not be converted into 'B'; \n"
|
||||
"this is because accessing `x` results in `ChildClass` in the former type and `BaseClass` in the latter type, and `ChildClass` is not "
|
||||
"exactly `BaseClass`" == toString(result.errors.at(0))
|
||||
);
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK(toString(result.errors.at(0)) == "Type 'A' could not be converted into 'B'; at [read \"x\"], ChildClass is not exactly BaseClass");
|
||||
else
|
||||
{
|
||||
|
|
|
@ -9,9 +9,7 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauClipNestedAndRecursiveUnion)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauPreventReentrantTypeFunctionReduction)
|
||||
LUAU_FASTFLAG(LuauDontForgetToReduceUnionFunc)
|
||||
|
||||
TEST_SUITE_BEGIN("DefinitionTests");
|
||||
|
@ -545,8 +543,6 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauClipNestedAndRecursiveUnion, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local t: {[string]: string} = {}
|
||||
|
||||
|
@ -592,7 +588,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "vector3_overflow")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauPreventReentrantTypeFunctionReduction, true};
|
||||
// We set this to zero to ensure that we either run to completion or stack overflow here.
|
||||
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauSubtypingFixTailPack)
|
||||
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
||||
|
@ -1306,7 +1306,16 @@ f(function(a, b, c, ...) return a + b end)
|
|||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
std::string expected;
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
if (FFlag::LuauInstantiateInSubtyping && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
expected = "Type\n\t"
|
||||
"'<a>(number, number, a) -> number'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number, number) -> number'"
|
||||
"\ncaused by:\n"
|
||||
" Argument count mismatch. Function expects 3 arguments, but only 2 are specified";
|
||||
}
|
||||
else if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
expected = R"(Type
|
||||
'<a>(number, number, a) -> number'
|
||||
|
@ -1315,6 +1324,15 @@ could not be converted into
|
|||
caused by:
|
||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)";
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
expected = "Type\n\t"
|
||||
"'(number, number, *error-type*) -> number'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number, number) -> number'"
|
||||
"\ncaused by:\n"
|
||||
" Argument count mismatch. Function expects 3 arguments, but only 2 are specified";
|
||||
}
|
||||
else
|
||||
{
|
||||
expected = R"(Type
|
||||
|
@ -1519,7 +1537,14 @@ local b: B = a
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
|
||||
? "Type\n\t"
|
||||
"'(number, number) -> string'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number) -> string'"
|
||||
"\ncaused by:\n"
|
||||
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified"
|
||||
: R"(Type
|
||||
'(number, number) -> string'
|
||||
could not be converted into
|
||||
'(number) -> string'
|
||||
|
@ -1542,7 +1567,14 @@ local b: B = a
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
|
||||
"'(number, number) -> string'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number, string) -> string'"
|
||||
"\ncaused by:\n"
|
||||
" Argument #2 type is not compatible.\n"
|
||||
"Type 'string' could not be converted into 'number'"
|
||||
: R"(Type
|
||||
'(number, number) -> string'
|
||||
could not be converted into
|
||||
'(number, string) -> string'
|
||||
|
@ -1566,7 +1598,13 @@ local b: B = a
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
|
||||
"'(number, number) -> number'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number, number) -> (number, boolean)'"
|
||||
"\ncaused by:\n"
|
||||
" Function only returns 1 value, but 2 are required here"
|
||||
: R"(Type
|
||||
'(number, number) -> number'
|
||||
could not be converted into
|
||||
'(number, number) -> (number, boolean)'
|
||||
|
@ -1589,7 +1627,14 @@ local b: B = a
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
|
||||
"'(number, number) -> string'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number, number) -> number'"
|
||||
"\ncaused by:\n"
|
||||
" Return type is not compatible.\n"
|
||||
"Type 'string' could not be converted into 'number'"
|
||||
: R"(Type
|
||||
'(number, number) -> string'
|
||||
could not be converted into
|
||||
'(number, number) -> number'
|
||||
|
@ -1613,7 +1658,14 @@ local b: B = a
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ? "Type\n\t"
|
||||
"'(number, number) -> (number, string)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number, number) -> (number, boolean)'"
|
||||
"\ncaused by:\n"
|
||||
" Return #2 type is not compatible.\n"
|
||||
"Type 'string' could not be converted into 'boolean'"
|
||||
: R"(Type
|
||||
'(number, number) -> (number, string)'
|
||||
could not be converted into
|
||||
'(number, number) -> (number, boolean)'
|
||||
|
@ -1769,6 +1821,24 @@ end
|
|||
R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)"
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type
|
||||
'(string) -> string'
|
||||
could not be converted into
|
||||
'((number) -> number)?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example:
|
||||
Type
|
||||
'(string) -> string'
|
||||
could not be converted into
|
||||
'(number) -> number'
|
||||
caused by:
|
||||
Argument #1 type is not compatible.
|
||||
Type 'number' could not be converted into 'string')");
|
||||
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
@ -1812,15 +1882,31 @@ function t:b() return 2 end -- not OK
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(
|
||||
R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
CHECK_EQ(
|
||||
"Type\n\t"
|
||||
"'(*error-type*) -> number'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'() -> number'\n"
|
||||
"caused by:\n"
|
||||
" Argument count mismatch. Function expects 1 argument, but none are specified",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(
|
||||
R"(Type
|
||||
'(*error-type*) -> number'
|
||||
could not be converted into
|
||||
'() -> number'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 1 argument, but none are specified)",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic")
|
||||
|
@ -2078,7 +2164,13 @@ z = y -- Not OK, so the line is colorable
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected =
|
||||
(FFlag::LuauImproveTypePathsInErrors)
|
||||
? "Type\n\t"
|
||||
R"('(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)')"
|
||||
"\ncould not be converted into\n\t"
|
||||
R"('("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)"
|
||||
: R"(Type
|
||||
'(("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> boolean) & (("blue" | "red") -> ("blue") -> ("blue") -> false) & (("blue" | "red") -> ("red") -> ("red") -> false) & (("blue") -> ("blue") -> ("blue" | "red") -> false) & (("red") -> ("red") -> ("blue" | "red") -> false)'
|
||||
could not be converted into
|
||||
'("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)";
|
||||
|
@ -2412,7 +2504,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
CHECK(
|
||||
"Type pack 'string' could not be converted into 'number'; \n"
|
||||
"this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a "
|
||||
"subtype of `number`" == toString(result.errors.at(0))
|
||||
);
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK(toString(result.errors.at(0)) == "Type pack 'string' could not be converted into 'number'; at [0], string is not a subtype of number");
|
||||
else
|
||||
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
|
||||
|
@ -2437,7 +2536,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_after_num_or_str")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
CHECK(
|
||||
"Type pack 'string' could not be converted into 'number'; \n"
|
||||
"this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a "
|
||||
"subtype of `number`" == toString(result.errors.at(0))
|
||||
);
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK(toString(result.errors.at(0)) == "Type pack 'string' could not be converted into 'number'; at [0], string is not a subtype of number");
|
||||
else
|
||||
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
|
||||
|
@ -3033,8 +3138,6 @@ TEST_CASE_FIXTURE(Fixture, "hidden_variadics_should_not_break_subtyping")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_wrap_result_call")
|
||||
{
|
||||
ScopedFastFlag luauSubtypingFixTailPack{FFlag::LuauSubtypingFixTailPack, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(a, b)
|
||||
coroutine.wrap(a)(b)
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
#include "ScopedFlags.h"
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -875,7 +876,13 @@ y.a.c = y
|
|||
CHECK(mismatch);
|
||||
CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T<string>?, d: number }, b: number }");
|
||||
CHECK_EQ(toString(mismatch->wantedType), "T<string>");
|
||||
std::string reason = "at [read \"a\"][read \"d\"], number is not exactly string\n\tat [read \"b\"], number is not exactly string";
|
||||
std::string reason =
|
||||
(FFlag::LuauImproveTypePathsInErrors)
|
||||
? "\nthis is because \n\t"
|
||||
" * accessing `a.d` results in `number` in the former type and `string` in the latter type, and `number` is not exactly "
|
||||
"`string`\n\t"
|
||||
" * accessing `b` results in `number` in the former type and `string` in the latter type, and `number` is not exactly `string`"
|
||||
: "at [read \"a\"][read \"d\"], number is not exactly string\n\tat [read \"b\"], number is not exactly string";
|
||||
CHECK_EQ(mismatch->reason, reason);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("IntersectionTypes");
|
||||
|
||||
|
@ -357,12 +358,21 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
|
|||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
|
||||
? "Type\n\t"
|
||||
"'(string, number) -> string'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(string) -> string'\n"
|
||||
"caused by:\n"
|
||||
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified"
|
||||
: R"(Type
|
||||
'(string, number) -> string'
|
||||
could not be converted into
|
||||
'(string) -> string'
|
||||
caused by:
|
||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
|
||||
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
|
||||
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
|
||||
|
@ -387,7 +397,14 @@ TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors)
|
||||
? "Type\n\t"
|
||||
"'(string, number) -> string'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(string) -> string'\n"
|
||||
"caused by:\n"
|
||||
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified"
|
||||
: R"(Type
|
||||
'(string, number) -> string'
|
||||
could not be converted into
|
||||
'(string) -> string'
|
||||
|
@ -430,7 +447,20 @@ Type 'number' could not be converted into 'X')";
|
|||
R"(Type 'number' could not be converted into 'X & Y & Z'; type number (number) is not a subtype of X & Y & Z[0] (X)
|
||||
type number (number) is not a subtype of X & Y & Z[1] (Y)
|
||||
type number (number) is not a subtype of X & Y & Z[2] (Z))";
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type "
|
||||
"'number'"
|
||||
" could not be converted into "
|
||||
"'X & Y & Z'; \n"
|
||||
"this is because \n\t"
|
||||
" * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t"
|
||||
" * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t"
|
||||
" * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ(dcrExprected, toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
|
@ -450,7 +480,23 @@ end
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type pack "
|
||||
"'X & Y & Z'"
|
||||
" could not be converted into "
|
||||
"'number'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st entry in the type pack has the 1st component of the intersection as `X` and the 1st entry in the "
|
||||
"type pack is `number`, and `X` is not a subtype of `number`\n\t"
|
||||
" * in the 1st entry in the type pack has the 2nd component of the intersection as `Y` and the 1st entry in the "
|
||||
"type pack is `number`, and `Y` is not a subtype of `number`\n\t"
|
||||
" * in the 1st entry in the type pack has the 3rd component of the intersection as `Z` and the 1st entry in the "
|
||||
"type pack is `number`, and `Z` is not a subtype of `number`";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK_EQ(
|
||||
R"(Type pack 'X & Y & Z' could not be converted into 'number'; type X & Y & Z[0][0] (X) is not a subtype of number[0] (number)
|
||||
|
@ -503,7 +549,19 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type "
|
||||
"'boolean & false'"
|
||||
" could not be converted into "
|
||||
"'true'; \n"
|
||||
"this is because \n\t"
|
||||
" * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
|
||||
" * the 2nd component of the intersection is `false`, which is not a subtype of `true`";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK_EQ(
|
||||
R"(Type 'boolean & false' could not be converted into 'true'; type boolean & false[0] (boolean) is not a subtype of true (true)
|
||||
|
@ -527,8 +585,21 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
// TODO: odd stringification of `false & (boolean & false)`.)
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type "
|
||||
"'boolean & false & false'"
|
||||
" could not be converted into "
|
||||
"'true'; \n"
|
||||
"this is because \n\t"
|
||||
" * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t"
|
||||
" * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
|
||||
" * the 3rd component of the intersection is `false`, which is not a subtype of `true`";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ(
|
||||
R"(Type 'boolean & false & false' could not be converted into 'true'; type boolean & false & false[0] (false) is not a subtype of true (true)
|
||||
type boolean & false & false[1] (boolean) is not a subtype of true (true)
|
||||
|
@ -550,7 +621,39 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
|
|||
local z : (number) -> number = x -- Not OK
|
||||
end
|
||||
)");
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected1 =
|
||||
"Type\n\t"
|
||||
"'((number?) -> number?) & ((string?) -> string?)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(nil) -> nil'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`";
|
||||
|
||||
const std::string expected2 =
|
||||
"Type\n\t"
|
||||
"'((number?) -> number?) & ((string?) -> string?)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number) -> number'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
|
||||
"union as `nil` and it returns the 1st entry in the type pack is `number`, and `nil` is not a subtype of `number`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `string` and it returns the 1st entry in the type pack is `number`, and `string` is not a subtype of `number`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
|
||||
"union as `nil` and it returns the 1st entry in the type pack is `number`, and `nil` is not a subtype of `number`\n\t"
|
||||
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `string?` and it takes the 1st "
|
||||
"entry in the type pack is `number`, and `string?` is not a supertype of `number`";
|
||||
|
||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
const std::string expected1 = R"(Type
|
||||
|
@ -568,6 +671,15 @@ could not be converted into
|
|||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
'((number?) -> number?) & ((string?) -> string?)'
|
||||
could not be converted into
|
||||
'(number) -> number'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -592,11 +704,23 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'((number) -> number) & ((string) -> string)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(boolean | number) -> boolean | number'; none of the intersection parts are compatible";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'((number) -> number) & ((string) -> string)'
|
||||
could not be converted into
|
||||
'(boolean | number) -> boolean | number'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||
|
@ -609,16 +733,42 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected =
|
||||
(FFlag::LuauSolverV2)
|
||||
? R"(Type '{ p: number?, q: number?, r: number? } & { p: number?, q: string? }' could not be converted into '{ p: nil }'; type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[0][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil)
|
||||
type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[1][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil))"
|
||||
:
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type "
|
||||
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
|
||||
" could not be converted into "
|
||||
"'{ p: nil }'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
|
||||
"accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t"
|
||||
" * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and "
|
||||
"accessing `p` results in `nil`, and `number` is not exactly `nil`";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected =
|
||||
R"(Type
|
||||
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
|
||||
could not be converted into
|
||||
'{| p: nil |}'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected =
|
||||
(FFlag::LuauSolverV2)
|
||||
? R"(Type '{ p: number?, q: number?, r: number? } & { p: number?, q: string? }' could not be converted into '{ p: nil }'; type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[0][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil)
|
||||
type { p: number?, q: number?, r: number? } & { p: number?, q: string? }[1][read "p"][0] (number) is not exactly { p: nil }[read "p"] (nil))"
|
||||
:
|
||||
R"(Type
|
||||
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
|
||||
could not be converted into
|
||||
'{| p: nil |}'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||
|
@ -630,7 +780,28 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'{ p: number?, q: any } & { p: unknown, q: string? }'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'{ p: string?, q: number? }'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
|
||||
"accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t"
|
||||
" * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st "
|
||||
"component of the union as `string`, and `number?` is not exactly `string`\n\t"
|
||||
" * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in "
|
||||
"`number?`, and `any` is not exactly `number?`\n\t"
|
||||
" * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in "
|
||||
"`string?`, and `unknown` is not exactly `string?`\n\t"
|
||||
" * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and "
|
||||
"accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t"
|
||||
" * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st "
|
||||
"component of the union as `number`, and `string?` is not exactly `number`";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(
|
||||
|
@ -646,6 +817,15 @@ could not be converted into
|
|||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
'{| p: number?, q: any |} & {| p: unknown, q: string? |}'
|
||||
could not be converted into
|
||||
'{| p: string?, q: number? |}'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -678,7 +858,52 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected1 =
|
||||
"Type\n\t"
|
||||
"'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(nil) -> { p: number, q: number, r: number }'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"intersection as `{ p: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ p: "
|
||||
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
|
||||
"intersection as `{ q: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ q: "
|
||||
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"intersection as `{ p: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ p: "
|
||||
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
|
||||
"intersection as `{ r: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ r: "
|
||||
"number }` is not a subtype of `{ p: number, q: number, r: number }`";
|
||||
|
||||
const std::string expected2 =
|
||||
"Type\n\t"
|
||||
"'((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number?) -> { p: number, q: number, r: number }'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"intersection as `{ p: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ p: "
|
||||
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
|
||||
"intersection as `{ q: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ q: "
|
||||
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"intersection as `{ p: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ p: "
|
||||
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 2nd component of the "
|
||||
"intersection as `{ r: number }` and it returns the 1st entry in the type pack is `{ p: number, q: number, r: number }`, and `{ r: "
|
||||
"number }` is not a subtype of `{ p: number, q: number, r: number }`\n\t"
|
||||
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `string?` and it takes the 1st "
|
||||
"entry in the type pack has the 1st component of the union as `number`, and `string?` is not a supertype of `number`";
|
||||
|
||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(
|
||||
|
@ -703,6 +928,17 @@ could not be converted into
|
|||
toString(result.errors[1])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(
|
||||
R"(Type
|
||||
'((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})'
|
||||
could not be converted into
|
||||
'(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -730,6 +966,15 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
|
|||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
'((number?) -> a | number) & ((string?) -> a | string)'
|
||||
could not be converted into
|
||||
'(number?) -> a'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -757,6 +1002,15 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
|
|||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
'((a?) -> a | b) & ((c?) -> b | c)'
|
||||
could not be converted into
|
||||
'(a?) -> (a & c) | b'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -778,7 +1032,35 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
|
|||
end
|
||||
end
|
||||
)");
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected1 =
|
||||
"Type\n\t"
|
||||
"'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(nil, a...) -> (nil, b...)'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`";
|
||||
|
||||
const std::string expected2 =
|
||||
"Type\n\t"
|
||||
"'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(nil, b...) -> (nil, a...)'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`";
|
||||
|
||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(
|
||||
|
@ -798,6 +1080,15 @@ could not be converted into
|
|||
toString(result.errors[1])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'
|
||||
could not be converted into
|
||||
'(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -824,11 +1115,23 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'((nil) -> unknown) & ((number) -> number)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number?) -> number?'; none of the intersection parts are compatible";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'((nil) -> unknown) & ((number) -> number)'
|
||||
could not be converted into
|
||||
'(number?) -> number?'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
|
||||
|
@ -846,11 +1149,23 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'((number) -> number?) & ((unknown) -> string?)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number?) -> nil'; none of the intersection parts are compatible";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'((number) -> number?) & ((unknown) -> string?)'
|
||||
could not be converted into
|
||||
'(number?) -> nil'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
||||
|
@ -864,7 +1179,36 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected1 =
|
||||
"Type\n\t"
|
||||
"'((nil) -> never) & ((number) -> number)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number?) -> number'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function takes the 1st entry in the type pack which is `number` and it takes the 1st "
|
||||
"entry in the type pack has the 2nd component of the union as `nil`, and `number` is not a supertype of `nil`\n\t"
|
||||
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `nil` and it takes the 1st "
|
||||
"entry in the type pack has the 1st component of the union as `number`, and `nil` is not a supertype of `number`";
|
||||
|
||||
const std::string expected2 =
|
||||
"Type\n\t"
|
||||
"'((nil) -> never) & ((number) -> number)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number?) -> never'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which is `number` and it returns the "
|
||||
"1st entry in the type pack is `never`, and `number` is not a subtype of `never`\n\t"
|
||||
" * in the 1st component of the intersection, the function takes the 1st entry in the type pack which is `number` and it takes the 1st "
|
||||
"entry in the type pack has the 2nd component of the union as `nil`, and `number` is not a supertype of `nil`\n\t"
|
||||
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `nil` and it takes the 1st "
|
||||
"entry in the type pack has the 1st component of the union as `number`, and `nil` is not a supertype of `number`";
|
||||
|
||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(
|
||||
|
@ -885,6 +1229,15 @@ could not be converted into
|
|||
toString(result.errors[1])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
'((nil) -> never) & ((number) -> number)'
|
||||
could not be converted into
|
||||
'(number?) -> never'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -907,7 +1260,40 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected1 =
|
||||
"Type\n\t"
|
||||
"'((never) -> string?) & ((number) -> number?)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(never) -> nil'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`";
|
||||
|
||||
const std::string expected2 =
|
||||
"Type\n\t"
|
||||
"'((never) -> string?) & ((number) -> number?)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number?) -> nil'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t"
|
||||
" * in the 1st component of the intersection, the function takes the 1st entry in the type pack which is `number` and it takes the 1st "
|
||||
"entry in the type pack has the 2nd component of the union as `nil`, and `number` is not a supertype of `nil`\n\t"
|
||||
" * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the "
|
||||
"union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`\n\t"
|
||||
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `never` and it takes the 1st "
|
||||
"entry in the type pack has the 1st component of the union as `number`, and `never` is not a supertype of `number`\n\t"
|
||||
" * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `never` and it takes the 1st "
|
||||
"entry in the type pack has the 2nd component of the union as `nil`, and `never` is not a supertype of `nil`";
|
||||
|
||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
const std::string expected1 = R"(Type
|
||||
|
@ -926,6 +1312,15 @@ could not be converted into
|
|||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
'((never) -> string?) & ((number) -> number?)'
|
||||
could not be converted into
|
||||
'(number?) -> nil'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -950,11 +1345,23 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'((number?) -> (...number)) & ((string?) -> number | string)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(number | string) -> (number, number?)'; none of the intersection parts are compatible";
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'((number?) -> (...number)) & ((string?) -> number | string)'
|
||||
could not be converted into
|
||||
'(number | string) -> (number, number?)'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
|
||||
|
@ -1022,6 +1429,15 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
|
|||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
'(() -> (a...)) & (() -> (number?, a...))'
|
||||
could not be converted into
|
||||
'() -> number'; none of the intersection parts are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -1045,7 +1461,21 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'((a...) -> ()) & ((number, a...) -> number)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'((a...) -> ()) & ((number, a...) -> number)'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in "
|
||||
"the latter type, and `()` is not a subtype of `number`\n\t"
|
||||
" * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of "
|
||||
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`";
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK_EQ(
|
||||
R"(Type
|
||||
|
@ -1056,6 +1486,16 @@ could not be converted into
|
|||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
CHECK_EQ(
|
||||
R"(Type
|
||||
'((a...) -> ()) & ((number, a...) -> number)'
|
||||
could not be converted into
|
||||
'(number?) -> ()'; none of the intersection parts are compatible)",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -461,7 +462,14 @@ local b: B.T = a
|
|||
CheckResult result = frontend.check("game/C");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n"
|
||||
"this is because accessing `x` results in `number` in the former type and `string` in the latter type, and "
|
||||
"`number` is not exactly `string`";
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK(
|
||||
toString(result.errors.at(0)) ==
|
||||
|
@ -507,7 +515,14 @@ local b: B.T = a
|
|||
CheckResult result = frontend.check("game/D");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n"
|
||||
"this is because accessing `x` results in `number` in the former type and `string` in the latter type, and "
|
||||
"`number` is not exactly `string`";
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK(
|
||||
toString(result.errors.at(0)) ==
|
||||
|
|
|
@ -79,4 +79,5 @@ end
|
|||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauDoNotGeneralizeInTypeFunctions)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferOperators");
|
||||
|
||||
|
@ -815,8 +814,6 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "and_binexps_dont_unify")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDoNotGeneralizeInTypeFunctions, true};
|
||||
|
||||
// `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
|
||||
|
|
|
@ -11,14 +11,15 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification);
|
||||
LUAU_FASTFLAG(LuauStoreCSTData);
|
||||
LUAU_FASTINT(LuauNormalizeCacheLimit);
|
||||
LUAU_FASTINT(LuauTarjanChildLimit);
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||
LUAU_FASTINT(LuauNormalizeCacheLimit)
|
||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("ProvisionalTests");
|
||||
|
||||
|
@ -873,7 +874,15 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty
|
|||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
const std::string expected = (FFlag::LuauImproveTypePathsInErrors) ?
|
||||
R"(Type
|
||||
'{| x: number? |}'
|
||||
could not be converted into
|
||||
'{| x: number |}'
|
||||
caused by:
|
||||
Property 'x' is not compatible.
|
||||
Type 'number?' could not be converted into 'number' in an invariant context)"
|
||||
: R"(Type
|
||||
'{| x: number? |}'
|
||||
could not be converted into
|
||||
'{| x: number |}'
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeSingletons");
|
||||
|
||||
|
@ -364,7 +365,16 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'{ [\"\\n\"]: number }'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'{ [\"<>\"]: number }'";
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK(
|
||||
"Type\n"
|
||||
" '{ [\"\\n\"]: number }'\n"
|
||||
|
@ -440,12 +450,23 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
const std::string expectedError = R"(Type
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expectedError = "Type\n\t"
|
||||
"'{ result: string, success: boolean }'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'Err<number> | Ok<string>'";
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expectedError = R"(Type
|
||||
'{ result: string, success: boolean }'
|
||||
could not be converted into
|
||||
'Err<number> | Ok<string>')";
|
||||
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options")
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "Luau/Error.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
|
@ -17,19 +18,18 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
|
||||
LUAU_FASTFLAG(LuauDontInPlaceMutateTableType)
|
||||
LUAU_FASTFLAG(LuauAllowNonSharedTableTypesInLiteral)
|
||||
LUAU_FASTFLAG(LuauFollowTableFreeze)
|
||||
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
|
||||
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
|
||||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||
LUAU_FASTFLAG(LuauSearchForRefineableType)
|
||||
LUAU_FASTFLAG(LuauDoNotGeneralizeInTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -922,7 +922,16 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify")
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
CHECK(
|
||||
"Type pack '{number}' could not be converted into '{string}'; \n"
|
||||
"this is because in the 1st entry in the type pack, the result of indexing is `number` in the former type and `string` in the latter "
|
||||
"type, "
|
||||
"and `number` is not exactly `string`" == toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
// CLI-114879 - Error path reporting is not great
|
||||
CHECK(
|
||||
|
@ -1805,7 +1814,16 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
CHECK_EQ(
|
||||
"Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }'; \n"
|
||||
"this is because the 1st entry in the type pack is `{ x: number }` in the former type and `{ x: number, y: number, z: number }` in the "
|
||||
"latter type, and `{ x: number }` is not a subtype of `{ x: number, y: number, z: number }`",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK_EQ(
|
||||
"Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }';"
|
||||
|
@ -2435,7 +2453,15 @@ local b: B = a
|
|||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
CHECK(
|
||||
"Type 'A' could not be converted into 'B'; \n"
|
||||
"this is because accessing `y` results in `number` in the former type and `string` in the latter type, and `number` is not exactly "
|
||||
"`string`" == toString(result.errors.at(0))
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK(toString(result.errors.at(0)) == R"(Type 'A' could not be converted into 'B'; at [read "y"], number is not exactly string)");
|
||||
else
|
||||
{
|
||||
|
@ -2462,7 +2488,15 @@ local b: B = a
|
|||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
CHECK(
|
||||
"Type 'A' could not be converted into 'B'; \n"
|
||||
"this is because accessing `b.y` results in `number` in the former type and `string` in the latter type, and `number` is not exactly "
|
||||
"`string`" == toString(result.errors.at(0))
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK(toString(result.errors.at(0)) == R"(Type 'A' could not be converted into 'B'; at [read "b"][read "y"], number is not exactly string)");
|
||||
else
|
||||
{
|
||||
|
@ -2489,7 +2523,17 @@ local b2 = setmetatable({ x = 2, y = 4 }, { __call = function(s, t) end });
|
|||
local c2: typeof(a2) = b2
|
||||
)");
|
||||
|
||||
const std::string expected1 = R"(Type 'b1' could not be converted into 'a1'
|
||||
const std::string expected1 = (FFlag::LuauImproveTypePathsInErrors) ?
|
||||
R"(Type 'b1' could not be converted into 'a1'
|
||||
caused by:
|
||||
Type
|
||||
'{ x: number, y: string }'
|
||||
could not be converted into
|
||||
'{ x: number, y: number }'
|
||||
caused by:
|
||||
Property 'y' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)"
|
||||
: R"(Type 'b1' could not be converted into 'a1'
|
||||
caused by:
|
||||
Type
|
||||
'{ x: number, y: string }'
|
||||
|
@ -2498,7 +2542,20 @@ could not be converted into
|
|||
caused by:
|
||||
Property 'y' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
const std::string expected2 = R"(Type 'b2' could not be converted into 'a2'
|
||||
const std::string expected2 = (FFlag::LuauImproveTypePathsInErrors) ?
|
||||
R"(Type 'b2' could not be converted into 'a2'
|
||||
caused by:
|
||||
Type
|
||||
'{ __call: <a, b>(a, b) -> () }'
|
||||
could not be converted into
|
||||
'{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible.
|
||||
Type
|
||||
'<a, b>(a, b) -> ()'
|
||||
could not be converted into
|
||||
'<a>(a) -> ()'; different number of generic type parameters)"
|
||||
: R"(Type 'b2' could not be converted into 'a2'
|
||||
caused by:
|
||||
Type
|
||||
'{ __call: <a, b>(a, b) -> () }'
|
||||
|
@ -2511,7 +2568,23 @@ Type
|
|||
could not be converted into
|
||||
'<a>(a) -> ()'; different number of generic type parameters)";
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
// The assignment of c2 to b2 is, surprisingly, allowed under the new
|
||||
// solver for two reasons:
|
||||
//
|
||||
// First, both of the __call functions have hidden ...any arguments
|
||||
// because their exact definition is available.
|
||||
//
|
||||
// Second, nil <: unknown, so we consider that parameter to be optional.
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(
|
||||
"Type 'b1' could not be converted into 'a1'; \n"
|
||||
"this is because in the table portion, accessing `y` results in `string` in the former type and `number` in the latter type, and "
|
||||
"`string` is not exactly `number`" == toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
// The assignment of c2 to b2 is, surprisingly, allowed under the new
|
||||
// solver for two reasons:
|
||||
|
@ -2576,7 +2649,15 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
|||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
CHECK(
|
||||
"Type 'A' could not be converted into 'B'; \n"
|
||||
"this is because the index type is `number` in the former type and `string` in the latter type, and `number` is not exactly `string`" ==
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK("Type 'A' could not be converted into 'B'; at indexer(), number is not exactly string" == toString(result.errors[0]));
|
||||
}
|
||||
|
@ -2602,7 +2683,15 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
|
|||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
CHECK(
|
||||
"Type 'A' could not be converted into 'B'; \n"
|
||||
"this is because the result of indexing is `number` in the former type and `string` in the latter type, and `number` is not exactly "
|
||||
"`string`" == toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK("Type 'A' could not be converted into 'B'; at indexResult(), number is not exactly string" == toString(result.errors[0]));
|
||||
}
|
||||
|
@ -2651,7 +2740,13 @@ local y: number = tmp.p.y
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
CHECK(
|
||||
"Type 'tmp' could not be converted into 'HasSuper'; \n"
|
||||
"this is because accessing `p` results in `{ x: number, y: number }` in the former type and `Super` in the latter type, and `{ x: "
|
||||
"number, y: number }` is not exactly `Super`" == toString(result.errors[0])
|
||||
);
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK(
|
||||
"Type 'tmp' could not be converted into 'HasSuper'; at [read \"p\"], { x: number, y: number } is not exactly Super" ==
|
||||
toString(result.errors[0])
|
||||
|
@ -3615,7 +3710,14 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys")
|
|||
local t: { [string]: number } = { 5, 6, 7 }
|
||||
)");
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
std::string expected =
|
||||
"Type '{number}' could not be converted into '{ [string]: number }'; \n"
|
||||
"this is because the index type is `number` in the former type and `string` in the latter type, and `number` is not exactly `string`";
|
||||
CHECK(toString(result.errors[0]) == expected);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(
|
||||
|
@ -3757,6 +3859,36 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_
|
|||
CHECK("typeof(string)" == toString(tm4->givenType));
|
||||
CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm4->wantedType));
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
|
||||
const std::string expected1 =
|
||||
R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
|
||||
const std::string expected2 =
|
||||
R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
|
||||
const std::string expected3 = R"(Type
|
||||
'"bar" | "baz"'
|
||||
could not be converted into
|
||||
't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
Not all union options are compatible.
|
||||
Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected3, toString(result.errors[2]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
|
@ -4338,11 +4470,25 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
std::string expected =
|
||||
"Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
|
||||
"\n\tat [read \"b\"], boolean is not exactly string"
|
||||
"\n\tat [read \"c\"], number is not exactly boolean";
|
||||
CHECK(toString(result.errors[0]) == expected);
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
std::string expected =
|
||||
"Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; \n"
|
||||
"this is because \n\t"
|
||||
" * accessing `a` results in `string` in the former type and `number` in the latter type, and `string` is not exactly `number`\n\t"
|
||||
" * accessing `b` results in `boolean` in the former type and `string` in the latter type, and `boolean` is not exactly `string`\n\t"
|
||||
" * accessing `c` results in `number` in the former type and `boolean` in the latter type, and `number` is not exactly `boolean`";
|
||||
CHECK(toString(result.errors[0]) == expected);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string expected =
|
||||
"Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
|
||||
"\n\tat [read \"b\"], boolean is not exactly string"
|
||||
"\n\tat [read \"c\"], number is not exactly boolean";
|
||||
CHECK(toString(result.errors[0]) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
|
||||
|
@ -4968,12 +5114,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(
|
||||
"Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; at [0].metatable(), { } is not a subtype of nil\n"
|
||||
"\ttype { @metatable { }, { } & { } }[0].table()[0] ({ }) is not a subtype of Class[0].table() (nil)\n"
|
||||
"\ttype { @metatable { }, { } & { } }[0].table()[1] ({ }) is not a subtype of Class[0].table() (nil)",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
CHECK_EQ(
|
||||
"Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st entry in the type pack, the metatable portion is `{ }` in the former type and `nil` in the latter type, and `{ }` "
|
||||
"is not a subtype of `nil`\n\t"
|
||||
" * in the 1st entry in the type pack, the table portion has the 1st component of the intersection as `{ }` and in the 1st entry "
|
||||
"in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`\n\t"
|
||||
" * in the 1st entry in the type pack, the table portion has the 2nd component of the intersection as `{ }` and in the 1st entry "
|
||||
"in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(
|
||||
"Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; at [0].metatable(), { } is not a subtype of nil\n"
|
||||
"\ttype { @metatable { }, { } & { } }[0].table()[0] ({ }) is not a subtype of Class[0].table() (nil)\n"
|
||||
"\ttype { @metatable { }, { } & { } }[0].table()[1] ({ }) is not a subtype of Class[0].table() (nil)",
|
||||
toString(result.errors[0])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
|
||||
|
@ -5071,7 +5235,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_in_literal")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDontInPlaceMutateTableType, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
|
@ -5098,11 +5261,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_in_literal")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_from_fuzzer")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDontInPlaceMutateTableType, true},
|
||||
{FFlag::LuauAllowNonSharedTableTypesInLiteral, true},
|
||||
};
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
// This would trigger an assert previously, so we really only care that
|
||||
// there are errors (and there will be: lots of syntax errors).
|
||||
|
@ -5113,11 +5272,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_from_fuzzer")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "write_only_table_field_duplicate")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDontInPlaceMutateTableType, true},
|
||||
{FFlag::LuauAllowNonSharedTableTypesInLiteral, true},
|
||||
};
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
||||
auto result = check(R"(
|
||||
type WriteOnlyTable = { write x: number }
|
||||
|
@ -5345,7 +5500,6 @@ TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauDoNotGeneralizeInTypeFunctions, true},
|
||||
{FFlag::LuauSearchForRefineableType, true},
|
||||
{FFlag::DebugLuauAssertOnForcedConstraint, true},
|
||||
};
|
||||
|
@ -5367,5 +5521,56 @@ TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference")
|
|||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauImproveTypePathsInErrors, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
type File = {
|
||||
type: "file",
|
||||
name: string,
|
||||
content: string?,
|
||||
}
|
||||
|
||||
type Dir = {
|
||||
type: "dir",
|
||||
name: string,
|
||||
children: { File | Dir }?,
|
||||
}
|
||||
|
||||
|
||||
type DirectoryChildren = { File | Dir }
|
||||
|
||||
local newtree: DirectoryChildren = {
|
||||
{
|
||||
type = "dir",
|
||||
name = "src",
|
||||
children = {
|
||||
{
|
||||
type = "file",
|
||||
path = "main.luau", -- I accidentally assign "path" instead of "name", causing a huge scary TypeError
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
std::string expected = "Type\n\t"
|
||||
"'{Dir | File | { children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, name: "
|
||||
"string, type: \"dir\" }}'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'DirectoryChildren'; \n"
|
||||
"this is because in the result of indexing has the 3rd component of the union as `{ children: ({Dir | File | { content: "
|
||||
"string?, path: string, type: \"file\" }} | {Dir | File})?, name: string, type: \"dir\" }` and the result of indexing is "
|
||||
"`Dir | File`, and `{ children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, "
|
||||
"name: string, type: \"dir\" }` is not exactly `Dir | File`";
|
||||
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -29,6 +29,7 @@ LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
|
|||
LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments)
|
||||
LUAU_FASTFLAG(LuauUnifyMetatableWithAny)
|
||||
LUAU_FASTFLAG(LuauExtraFollows)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1134,7 +1135,29 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
|||
end
|
||||
)");
|
||||
|
||||
if (FFlag::LuauInstantiateInSubtyping)
|
||||
if (FFlag::LuauInstantiateInSubtyping && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected =
|
||||
"Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule'"
|
||||
"\ncaused by:\n"
|
||||
" Property 'getStoreFieldName' is not compatible.\n"
|
||||
"Type\n\t"
|
||||
"'(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(Policies, FieldSpecifier) -> string'"
|
||||
"\ncaused by:\n"
|
||||
" Argument #2 type is not compatible.\n"
|
||||
"Type\n\t"
|
||||
"'FieldSpecifier'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'FieldSpecifier & {| from: number? |}'"
|
||||
"\ncaused by:\n"
|
||||
" Not all intersection parts are compatible.\n"
|
||||
"Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName'";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauInstantiateInSubtyping)
|
||||
{
|
||||
// though this didn't error before the flag, it seems as though it should error since fields of a table are invariant.
|
||||
// the user's intent would likely be that these "method" fields would be read-only, but without an annotation, accepting this should be
|
||||
|
@ -1911,4 +1934,17 @@ end
|
|||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local function foo(n : number): string return "" end
|
||||
local function bar(n: number, m: string) end
|
||||
local function concat_stuff(x, y)
|
||||
local z = foo(x)
|
||||
bar(y, z)
|
||||
end
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("TypePackTests");
|
||||
|
||||
|
@ -951,7 +953,19 @@ a = b
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
|
||||
const std::string expected = "Type\n\t"
|
||||
"'() -> (number, ...boolean)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'() -> (number, ...string)'; \n"
|
||||
"this is because it returns a tail of the variadic `boolean` in the former type and `string` in the latter "
|
||||
"type, and `boolean` is not a subtype of `string`";
|
||||
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
const std::string expected = "Type\n"
|
||||
" '() -> (number, ...boolean)'\n"
|
||||
|
@ -960,6 +974,16 @@ a = b
|
|||
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'() -> (number, ...boolean)'
|
||||
could not be converted into
|
||||
'() -> (number, ...string)'
|
||||
caused by:
|
||||
Type 'boolean' could not be converted into 'string')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
|
@ -1093,7 +1117,12 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
CHECK(
|
||||
toString(result.errors.at(0)) == "Type pack '...number' could not be converted into 'boolean'; \nthis is because it has a tail of "
|
||||
"`...number`, which is not a subtype of `boolean`"
|
||||
);
|
||||
else if (FFlag::LuauSolverV2)
|
||||
CHECK(
|
||||
toString(result.errors.at(0)) ==
|
||||
"Type pack '...number' could not be converted into 'boolean'; type ...number.tail() (...number) is not a subtype of boolean (boolean)"
|
||||
|
|
|
@ -10,6 +10,8 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("UnionTypes");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzzer_union_with_one_part_assertion")
|
||||
|
@ -538,7 +540,19 @@ end
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
|
||||
CHECK_EQ(
|
||||
toString(result.errors[0]),
|
||||
"Type 'X | Y | Z' could not be converted into '{ w: number }'; \n"
|
||||
"this is because \n\t"
|
||||
" * the 1st component of the union is `X`, which is not a subtype of `{ w: number }`\n\t"
|
||||
" * the 2nd component of the union is `Y`, which is not a subtype of `{ w: number }`\n\t"
|
||||
" * the 3rd component of the union is `Z`, which is not a subtype of `{ w: number }`"
|
||||
);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK_EQ(
|
||||
toString(result.errors[0]),
|
||||
|
@ -667,11 +681,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
// NOTE: union normalization will improve this message
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'(string) -> number'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'((number) -> string) | ((number) -> string)'; none of the union options are compatible";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'(string) -> number'
|
||||
could not be converted into
|
||||
'((number) -> string) | ((number) -> string)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_true_and_false")
|
||||
|
@ -757,11 +783,23 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'(number, a...) -> (number?, a...)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'(number, a...) -> (number?, a...)'
|
||||
could not be converted into
|
||||
'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
|
||||
|
@ -776,11 +814,23 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'(number) -> number?'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'(number) -> number?'
|
||||
could not be converted into
|
||||
'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
|
||||
|
@ -795,11 +845,23 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'() -> number | string'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(() -> (string, string)) | (() -> number)'; none of the union options are compatible";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'() -> number | string'
|
||||
could not be converted into
|
||||
'(() -> (string, string)) | (() -> number)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
|
||||
|
@ -814,11 +876,23 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'(...nil) -> (...number?)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'(...nil) -> (...number?)'
|
||||
could not be converted into
|
||||
'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
|
||||
|
@ -831,13 +905,29 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'(number) -> ()'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'((...number?) -> ()) | ((number?) -> ())'";
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CHECK(R"(Type
|
||||
'(number) -> ()'
|
||||
could not be converted into
|
||||
'((...number?) -> ()) | ((number?) -> ())')" == toString(result.errors[0]));
|
||||
}
|
||||
else if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'(number) -> ()'
|
||||
could not be converted into
|
||||
'((...number?) -> ()) | ((number?) -> ())'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
|
@ -860,11 +950,23 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type
|
||||
|
||||
if (FFlag::LuauImproveTypePathsInErrors)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'() -> (number?, ...number)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(() -> (...number)) | (() -> number)'; none of the union options are compatible";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type
|
||||
'() -> (number?, ...number)'
|
||||
could not be converted into
|
||||
'(() -> (...number)) | (() -> number)'; none of the union options are compatible)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")
|
||||
|
@ -991,4 +1093,17 @@ TEST_CASE_FIXTURE(Fixture, "suppress_errors_for_prop_lookup_of_a_union_that_incl
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "handle_multiple_optionals")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function f(a: string??)
|
||||
if a then
|
||||
print(a:sub(1,1))
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -554,6 +554,14 @@ TEST_CASE("chain")
|
|||
CHECK(toString(PathBuilder().index(0).mt().build()) == "[0].metatable()");
|
||||
}
|
||||
|
||||
TEST_CASE("human_property_then_metatable_portion")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
|
||||
CHECK(toStringHuman(PathBuilder().readProp("a").mt().build()) == "accessing `a` has the metatable portion as ");
|
||||
CHECK(toStringHuman(PathBuilder().writeProp("a").mt().build()) == "writing to `a` has the metatable portion as ");
|
||||
}
|
||||
|
||||
TEST_SUITE_END(); // TypePathToString
|
||||
|
||||
TEST_SUITE_BEGIN("TypePathBuilder");
|
||||
|
|
Loading…
Add table
Reference in a new issue