mirror of
https://github.com/luau-lang/luau.git
synced 2025-04-02 01:40:54 +01:00
Sync to upstream/release/666 (#1747)
Another week, another release. Happy spring! 🌷 ## New Type Solver - Add typechecking and autocomplete support for user-defined type functions! - Improve the display of type paths, making type mismatch errors far more human-readable. - Enhance various aspects of the `index` type function: support function type metamethods, fix crashes involving cyclic metatables, and forward `any` types through the type function. - Fix incorrect subtyping results involving the `buffer` type. - Fix crashes related to typechecking anonymous functions in nonstrict mode. ## AST - Retain source information for type packs, functions, and type functions. - Introduce `AstTypeOptional` to differentiate `T?` from `T | nil` in the AST. - Prevent the transpiler from advancing before tokens when the AST has errors. ## Autocomplete - Introduce demand-based cloning and better module isolation for fragment autocomplete, leading to a substantial speedup in performance. - Guard against recursive unions in `autocompleteProps`. ## Miscellaneous - #1720 (thank you!) ## Internal Contributors Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Talha Pathan <tpathan@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
e0b55a9cb1
commit
5f42e63a73
82 changed files with 3871 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);
|
void assignPropDocumentationSymbols(TableType::Props& props, const std::string& baseName);
|
||||||
|
|
||||||
std::string getBuiltinDefinitionSource();
|
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, TypeId ty, const std::string& packageName);
|
||||||
void addGlobalBinding(GlobalTypes& globals, const std::string& name, Binding binding);
|
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.
|
// Needed to register all available type functions for execution at later stages.
|
||||||
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
|
||||||
|
DenseHashMap<const AstStatTypeFunction*, ScopePtr> astTypeFunctionEnvironmentScopes{nullptr};
|
||||||
|
|
||||||
// Needed to resolve modules to make 'require' import types properly.
|
// Needed to resolve modules to make 'require' import types properly.
|
||||||
NotNull<ModuleResolver> moduleResolver;
|
NotNull<ModuleResolver> moduleResolver;
|
||||||
// Occasionally constraint generation needs to produce an ICE.
|
// Occasionally constraint generation needs to produce an ICE.
|
||||||
const NotNull<InternalErrorReporter> ice;
|
const NotNull<InternalErrorReporter> ice;
|
||||||
|
|
||||||
ScopePtr globalScope;
|
ScopePtr globalScope;
|
||||||
|
ScopePtr typeFunctionScope;
|
||||||
|
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope;
|
||||||
std::vector<RequireCycle> requireCycles;
|
std::vector<RequireCycle> requireCycles;
|
||||||
|
@ -140,6 +143,7 @@ struct ConstraintGenerator
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<InternalErrorReporter> ice,
|
NotNull<InternalErrorReporter> ice,
|
||||||
const ScopePtr& globalScope,
|
const ScopePtr& globalScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
DcrLogger* logger,
|
DcrLogger* logger,
|
||||||
NotNull<DataFlowGraph> dfg,
|
NotNull<DataFlowGraph> dfg,
|
||||||
|
|
|
@ -305,6 +305,7 @@ ModulePtr check(
|
||||||
NotNull<ModuleResolver> moduleResolver,
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
NotNull<FileResolver> fileResolver,
|
NotNull<FileResolver> fileResolver,
|
||||||
const ScopePtr& globalScope,
|
const ScopePtr& globalScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
FrontendOptions options,
|
FrontendOptions options,
|
||||||
TypeCheckLimits limits
|
TypeCheckLimits limits
|
||||||
|
@ -319,6 +320,7 @@ ModulePtr check(
|
||||||
NotNull<ModuleResolver> moduleResolver,
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
NotNull<FileResolver> fileResolver,
|
NotNull<FileResolver> fileResolver,
|
||||||
const ScopePtr& globalScope,
|
const ScopePtr& globalScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
FrontendOptions options,
|
FrontendOptions options,
|
||||||
TypeCheckLimits limits,
|
TypeCheckLimits limits,
|
||||||
|
|
|
@ -12,8 +12,9 @@ std::optional<TypeId> generalize(
|
||||||
NotNull<TypeArena> arena,
|
NotNull<TypeArena> arena,
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<Scope> scope,
|
NotNull<Scope> scope,
|
||||||
NotNull<DenseHashSet<TypeId>> bakedTypes,
|
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||||
TypeId ty,
|
TypeId ty,
|
||||||
/* avoid sealing tables*/ bool avoidSealingTables = false
|
/* avoid sealing tables*/ bool avoidSealingTables = false
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@ struct GlobalTypes
|
||||||
|
|
||||||
TypeArena globalTypes;
|
TypeArena globalTypes;
|
||||||
SourceModule globalNames; // names for symbols entered into globalScope
|
SourceModule globalNames; // names for symbols entered into globalScope
|
||||||
|
|
||||||
ScopePtr globalScope; // shared by all modules
|
ScopePtr globalScope; // shared by all modules
|
||||||
|
ScopePtr globalTypeFunctionScope; // shared by all modules
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -35,7 +35,7 @@ struct Scope
|
||||||
explicit Scope(TypePackId returnType); // root scope
|
explicit Scope(TypePackId returnType); // root scope
|
||||||
explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr.
|
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.
|
// All the children of this scope.
|
||||||
std::vector<NotNull<Scope>> children;
|
std::vector<NotNull<Scope>> children;
|
||||||
|
@ -59,6 +59,8 @@ struct Scope
|
||||||
|
|
||||||
std::optional<TypeId> lookup(Symbol sym) const;
|
std::optional<TypeId> lookup(Symbol sym) const;
|
||||||
std::optional<TypeId> lookupUnrefinedType(DefId def) const;
|
std::optional<TypeId> lookupUnrefinedType(DefId def) const;
|
||||||
|
|
||||||
|
std::optional<TypeId> lookupRValueRefinementType(DefId def) const;
|
||||||
std::optional<TypeId> lookup(DefId def) const;
|
std::optional<TypeId> lookup(DefId def) const;
|
||||||
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
|
std::optional<std::pair<TypeId, Scope*>> lookupEx(DefId def);
|
||||||
std::optional<std::pair<Binding*, Scope*>> lookupEx(Symbol sym);
|
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)
|
// 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<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;
|
RefinementMap refinements;
|
||||||
|
|
||||||
|
|
|
@ -622,7 +622,6 @@ struct UserDefinedFunctionData
|
||||||
AstStatTypeFunction* definition = nullptr;
|
AstStatTypeFunction* definition = nullptr;
|
||||||
|
|
||||||
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""};
|
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""};
|
||||||
DenseHashMap<Name, AstStatTypeFunction*> environment_DEPRECATED{""};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
#include "Luau/TypeOrPack.h"
|
#include "Luau/TypeOrPack.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -38,18 +40,29 @@ struct Reasonings
|
||||||
|
|
||||||
std::string toString()
|
std::string toString()
|
||||||
{
|
{
|
||||||
|
if (FFlag::LuauImproveTypePathsInErrors && reasons.empty())
|
||||||
|
return "";
|
||||||
|
|
||||||
// DenseHashSet ordering is entirely undefined, so we want to
|
// DenseHashSet ordering is entirely undefined, so we want to
|
||||||
// sort the reasons here to achieve a stable error
|
// sort the reasons here to achieve a stable error
|
||||||
// stringification.
|
// stringification.
|
||||||
std::sort(reasons.begin(), reasons.end());
|
std::sort(reasons.begin(), reasons.end());
|
||||||
std::string allReasons;
|
std::string allReasons = FFlag::LuauImproveTypePathsInErrors ? "\nthis is because " : "";
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (const std::string& reason : reasons)
|
for (const std::string& reason : reasons)
|
||||||
{
|
{
|
||||||
if (first)
|
if (FFlag::LuauImproveTypePathsInErrors)
|
||||||
first = false;
|
{
|
||||||
|
if (reasons.size() > 1)
|
||||||
|
allReasons += "\n\t * ";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
allReasons += "\n\t";
|
{
|
||||||
|
if (first)
|
||||||
|
first = false;
|
||||||
|
else
|
||||||
|
allReasons += "\n\t";
|
||||||
|
}
|
||||||
|
|
||||||
allReasons += reason;
|
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
|
// Evaluation of type functions should only be performed in the absence of parse errors in the source module
|
||||||
bool allowEvaluation = true;
|
bool allowEvaluation = true;
|
||||||
|
|
||||||
|
// Root scope in which the type function operates in, set up by ConstraintGenerator
|
||||||
|
ScopePtr rootScope;
|
||||||
|
|
||||||
// Output created by 'print' function
|
// Output created by 'print' function
|
||||||
std::vector<std::string> messages;
|
std::vector<std::string> messages;
|
||||||
|
|
||||||
|
|
|
@ -223,8 +223,6 @@ struct TypeFunctionClassType
|
||||||
std::optional<TypeFunctionTypeId> writeParent;
|
std::optional<TypeFunctionTypeId> writeParent;
|
||||||
|
|
||||||
TypeId classTy;
|
TypeId classTy;
|
||||||
|
|
||||||
std::string name_DEPRECATED;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TypeFunctionGenericType
|
struct TypeFunctionGenericType
|
||||||
|
|
|
@ -28,14 +28,8 @@ struct TypeFunctionRuntimeBuilderState
|
||||||
{
|
{
|
||||||
NotNull<TypeFunctionContext> ctx;
|
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
|
// 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{};
|
std::vector<std::string> errors{};
|
||||||
|
|
||||||
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
|
TypeFunctionRuntimeBuilderState(NotNull<TypeFunctionContext> ctx)
|
||||||
|
|
|
@ -42,9 +42,19 @@ struct Property
|
||||||
/// element.
|
/// element.
|
||||||
struct Index
|
struct Index
|
||||||
{
|
{
|
||||||
|
enum class Variant
|
||||||
|
{
|
||||||
|
Pack,
|
||||||
|
Union,
|
||||||
|
Intersection
|
||||||
|
};
|
||||||
|
|
||||||
/// The 0-based index to use for the lookup.
|
/// The 0-based index to use for the lookup.
|
||||||
size_t index;
|
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;
|
bool operator==(const Index& other) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -205,6 +215,9 @@ using Path = TypePath::Path;
|
||||||
/// terribly clear to end users of the Luau type system.
|
/// terribly clear to end users of the Luau type system.
|
||||||
std::string toString(const TypePath::Path& path, bool prefixDot = false);
|
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(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
||||||
std::optional<TypeOrPack> traverse(TypePackId 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)
|
void write(class AstTypeUnion* node)
|
||||||
{
|
{
|
||||||
writeNode(
|
writeNode(
|
||||||
|
|
|
@ -30,6 +30,7 @@ LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
|
||||||
|
|
||||||
static const std::unordered_set<std::string> kStatementStartingKeywords =
|
static const std::unordered_set<std::string> kStatementStartingKeywords =
|
||||||
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
{"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"};
|
||||||
|
@ -484,6 +485,21 @@ static void autocompleteProps(
|
||||||
AutocompleteEntryMap inner;
|
AutocompleteEntryMap inner;
|
||||||
std::unordered_set<TypeId> innerSeen;
|
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))
|
if (isNil(*iter))
|
||||||
{
|
{
|
||||||
++iter;
|
++iter;
|
||||||
|
|
|
@ -32,8 +32,8 @@ LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
|
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFreezeIgnorePersistent)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
|
LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
|
||||||
|
|
||||||
namespace Luau
|
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)
|
void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeCheckForAutocomplete)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
|
LUAU_ASSERT(!globals.globalTypes.types.isFrozen());
|
||||||
|
@ -399,14 +415,21 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& pair : globals.globalScope->bindings)
|
if (FFlag::LuauUserTypeFunTypecheck)
|
||||||
{
|
{
|
||||||
persist(pair.second.typeId);
|
finalizeGlobalBindings(globals.globalScope);
|
||||||
|
}
|
||||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
else
|
||||||
|
{
|
||||||
|
for (const auto& pair : globals.globalScope->bindings)
|
||||||
{
|
{
|
||||||
if (!ttv->name)
|
persist(pair.second.typeId);
|
||||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
|
||||||
|
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");
|
TypeId requireTy = getGlobalBinding(globals, "require");
|
||||||
attachTag(requireTy, kRequireTagName);
|
attachTag(requireTy, kRequireTagName);
|
||||||
attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
|
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)
|
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;
|
return false;
|
||||||
|
|
||||||
CloneState cloneState{context.solver->builtinTypes};
|
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))
|
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.
|
// Clone the input type, this will become our final result type after we mutate it.
|
||||||
CloneState cloneState{context.solver->builtinTypes};
|
CloneState cloneState{context.solver->builtinTypes};
|
||||||
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ FFlag::LuauFreezeIgnorePersistent);
|
TypeId resultType = shallowClone(inputType, *arena, cloneState, /* ignorePersistent */ true);
|
||||||
auto tableTy = getMutable<TableType>(resultType);
|
auto tableTy = getMutable<TableType>(resultType);
|
||||||
// `clone` should not break this.
|
// `clone` should not break this.
|
||||||
LUAU_ASSERT(tableTy);
|
LUAU_ASSERT(tableTy);
|
||||||
|
|
|
@ -9,13 +9,12 @@
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
|
|
||||||
|
|
||||||
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
|
// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
|
LUAU_FASTFLAGVARIABLE(LuauClonedTableAndFunctionTypesMustHaveScopes)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
|
LUAU_FASTFLAGVARIABLE(LuauDoNotClonePersistentBindings)
|
||||||
|
LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -134,7 +133,7 @@ protected:
|
||||||
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
ty = follow(ty, FollowOption::DisableLazyTypeThunks);
|
||||||
if (auto it = types->find(ty); it != types->end())
|
if (auto it = types->find(ty); it != types->end())
|
||||||
return it->second;
|
return it->second;
|
||||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
else if (ty->persistent && ty != forceTy)
|
||||||
return ty;
|
return ty;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -144,7 +143,7 @@ protected:
|
||||||
tp = follow(tp);
|
tp = follow(tp);
|
||||||
if (auto it = packs->find(tp); it != packs->end())
|
if (auto it = packs->find(tp); it != packs->end())
|
||||||
return it->second;
|
return it->second;
|
||||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
else if (tp->persistent && tp != forceTp)
|
||||||
return tp;
|
return tp;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -170,7 +169,7 @@ public:
|
||||||
|
|
||||||
if (auto clone = find(ty))
|
if (auto clone = find(ty))
|
||||||
return *clone;
|
return *clone;
|
||||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
else if (ty->persistent && ty != forceTy)
|
||||||
return ty;
|
return ty;
|
||||||
|
|
||||||
TypeId target = arena->addType(ty->ty);
|
TypeId target = arena->addType(ty->ty);
|
||||||
|
@ -196,7 +195,7 @@ public:
|
||||||
|
|
||||||
if (auto clone = find(tp))
|
if (auto clone = find(tp))
|
||||||
return *clone;
|
return *clone;
|
||||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
else if (tp->persistent && tp != forceTp)
|
||||||
return tp;
|
return tp;
|
||||||
|
|
||||||
TypePackId target = arena->addTypePack(tp->ty);
|
TypePackId target = arena->addTypePack(tp->ty);
|
||||||
|
@ -398,7 +397,7 @@ private:
|
||||||
ty = shallowClone(ty);
|
ty = shallowClone(ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cloneChildren(LazyType* t)
|
virtual void cloneChildren(LazyType* t)
|
||||||
{
|
{
|
||||||
if (auto unwrapped = t->unwrapped.load())
|
if (auto unwrapped = t->unwrapped.load())
|
||||||
t->unwrapped.store(shallowClone(unwrapped));
|
t->unwrapped.store(shallowClone(unwrapped));
|
||||||
|
@ -505,7 +504,7 @@ public:
|
||||||
|
|
||||||
if (auto clone = find(ty))
|
if (auto clone = find(ty))
|
||||||
return *clone;
|
return *clone;
|
||||||
else if (ty->persistent && (!FFlag::LuauFreezeIgnorePersistent || ty != forceTy))
|
else if (ty->persistent && ty != forceTy)
|
||||||
return ty;
|
return ty;
|
||||||
|
|
||||||
TypeId target = arena->addType(ty->ty);
|
TypeId target = arena->addType(ty->ty);
|
||||||
|
@ -539,7 +538,7 @@ public:
|
||||||
|
|
||||||
if (auto clone = find(tp))
|
if (auto clone = find(tp))
|
||||||
return *clone;
|
return *clone;
|
||||||
else if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || tp != forceTp))
|
else if (tp->persistent && tp != forceTp)
|
||||||
return tp;
|
return tp;
|
||||||
|
|
||||||
TypePackId target = arena->addTypePack(tp->ty);
|
TypePackId target = arena->addTypePack(tp->ty);
|
||||||
|
@ -553,6 +552,16 @@ public:
|
||||||
queue.emplace_back(target);
|
queue.emplace_back(target);
|
||||||
return 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)
|
TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
|
||||||
{
|
{
|
||||||
if (tp->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
|
if (tp->persistent && !ignorePersistent)
|
||||||
return tp;
|
return tp;
|
||||||
|
|
||||||
TypeCloner cloner{
|
TypeCloner cloner{
|
||||||
|
@ -569,7 +578,7 @@ TypePackId shallowClone(TypePackId tp, TypeArena& dest, CloneState& cloneState,
|
||||||
NotNull{&cloneState.seenTypes},
|
NotNull{&cloneState.seenTypes},
|
||||||
NotNull{&cloneState.seenTypePacks},
|
NotNull{&cloneState.seenTypePacks},
|
||||||
nullptr,
|
nullptr,
|
||||||
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? tp : nullptr
|
ignorePersistent ? tp : nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
return cloner.shallowClone(tp);
|
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)
|
TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool ignorePersistent)
|
||||||
{
|
{
|
||||||
if (typeId->persistent && (!FFlag::LuauFreezeIgnorePersistent || !ignorePersistent))
|
if (typeId->persistent && !ignorePersistent)
|
||||||
return typeId;
|
return typeId;
|
||||||
|
|
||||||
TypeCloner cloner{
|
TypeCloner cloner{
|
||||||
|
@ -585,7 +594,7 @@ TypeId shallowClone(TypeId typeId, TypeArena& dest, CloneState& cloneState, bool
|
||||||
cloneState.builtinTypes,
|
cloneState.builtinTypes,
|
||||||
NotNull{&cloneState.seenTypes},
|
NotNull{&cloneState.seenTypes},
|
||||||
NotNull{&cloneState.seenTypePacks},
|
NotNull{&cloneState.seenTypePacks},
|
||||||
FFlag::LuauFreezeIgnorePersistent && ignorePersistent ? typeId : nullptr,
|
ignorePersistent ? typeId : nullptr,
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
|
LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
|
LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
|
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
|
||||||
|
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -184,6 +185,7 @@ ConstraintGenerator::ConstraintGenerator(
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<BuiltinTypes> builtinTypes,
|
||||||
NotNull<InternalErrorReporter> ice,
|
NotNull<InternalErrorReporter> ice,
|
||||||
const ScopePtr& globalScope,
|
const ScopePtr& globalScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
DcrLogger* logger,
|
DcrLogger* logger,
|
||||||
NotNull<DataFlowGraph> dfg,
|
NotNull<DataFlowGraph> dfg,
|
||||||
|
@ -200,6 +202,7 @@ ConstraintGenerator::ConstraintGenerator(
|
||||||
, moduleResolver(moduleResolver)
|
, moduleResolver(moduleResolver)
|
||||||
, ice(ice)
|
, ice(ice)
|
||||||
, globalScope(globalScope)
|
, globalScope(globalScope)
|
||||||
|
, typeFunctionScope(typeFunctionScope)
|
||||||
, prepareModuleScope(std::move(prepareModuleScope))
|
, prepareModuleScope(std::move(prepareModuleScope))
|
||||||
, requireCycles(std::move(requireCycles))
|
, requireCycles(std::move(requireCycles))
|
||||||
, logger(logger)
|
, logger(logger)
|
||||||
|
@ -221,6 +224,14 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
||||||
|
|
||||||
rootScope->returnType = freshTypePack(scope);
|
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});
|
TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, rootScope, builtinTypes->anyTypePack, rootScope->returnType});
|
||||||
interiorTypes.emplace_back();
|
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> aliasDefinitionLocations;
|
||||||
std::unordered_map<Name, Location> classDefinitionLocations;
|
std::unordered_map<Name, Location> classDefinitionLocations;
|
||||||
|
|
||||||
|
bool hasTypeFunction = false;
|
||||||
|
ScopePtr typeFunctionEnvScope;
|
||||||
|
|
||||||
// In order to enable mutually-recursive type aliases, we need to
|
// In order to enable mutually-recursive type aliases, we need to
|
||||||
// populate the type bindings before we actually check any of the
|
// populate the type bindings before we actually check any of the
|
||||||
// alias statements.
|
// alias statements.
|
||||||
|
@ -744,6 +758,9 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
||||||
}
|
}
|
||||||
else if (auto function = stat->as<AstStatTypeFunction>())
|
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 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))
|
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;
|
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
|
// 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
|
// Additional pass for user-defined type functions to fill in their environments completely
|
||||||
for (AstStat* stat : block->body)
|
for (AstStat* stat : block->body)
|
||||||
{
|
{
|
||||||
if (auto function = stat->as<AstStatTypeFunction>())
|
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
|
// Find the type function we have already created
|
||||||
TypeFunctionInstanceType* mainTypeFun = nullptr;
|
TypeFunctionInstanceType* mainTypeFun = nullptr;
|
||||||
|
|
||||||
|
@ -843,51 +872,60 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
||||||
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
|
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
|
||||||
size_t level = 0;
|
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))
|
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);
|
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))
|
for (auto& [name, tf] : curr->privateTypeBindings)
|
||||||
continue;
|
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
|
||||||
|
|
||||||
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
for (auto& [name, tf] : curr->exportedTypeBindings)
|
||||||
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
|
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
|
||||||
|
|
||||||
|
level++;
|
||||||
}
|
}
|
||||||
|
|
||||||
level++;
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else if (mainTypeFun)
|
|
||||||
{
|
|
||||||
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
|
|
||||||
|
|
||||||
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
|
|
||||||
{
|
{
|
||||||
for (auto& [name, tf] : curr->privateTypeBindings)
|
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
|
||||||
{
|
{
|
||||||
if (userFuncData.environment_DEPRECATED.find(name))
|
for (auto& [name, tf] : curr->privateTypeBindings)
|
||||||
continue;
|
{
|
||||||
|
if (userFuncData.environment.find(name))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
||||||
userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition;
|
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& [name, tf] : curr->exportedTypeBindings)
|
for (auto& [name, tf] : curr->exportedTypeBindings)
|
||||||
{
|
{
|
||||||
if (userFuncData.environment_DEPRECATED.find(name))
|
if (userFuncData.environment.find(name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
|
||||||
userFuncData.environment_DEPRECATED[name] = 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)
|
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;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3094,7 +3190,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
|
||||||
signatureScope = childScope(fn, parent);
|
signatureScope = childScope(fn, parent);
|
||||||
|
|
||||||
// We need to assign returnType before creating bodyScope so that the
|
// 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);
|
returnType = freshTypePack(signatureScope);
|
||||||
signatureScope->returnType = returnType;
|
signatureScope->returnType = returnType;
|
||||||
|
|
||||||
|
@ -3520,6 +3616,10 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||||
TypeId exprType = check(scope, tof->expr).ty;
|
TypeId exprType = check(scope, tof->expr).ty;
|
||||||
result = exprType;
|
result = exprType;
|
||||||
}
|
}
|
||||||
|
else if (ty->is<AstTypeOptional>())
|
||||||
|
{
|
||||||
|
return builtinTypes->nilType;
|
||||||
|
}
|
||||||
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
|
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
|
||||||
{
|
{
|
||||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
@ -3882,9 +3982,18 @@ struct GlobalPrepopulator : AstVisitor
|
||||||
void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program)
|
void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program)
|
||||||
{
|
{
|
||||||
FragmentTypeCheckGlobalPrepopulator gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena};
|
FragmentTypeCheckGlobalPrepopulator gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena};
|
||||||
|
|
||||||
if (prepareModuleScope)
|
if (prepareModuleScope)
|
||||||
prepareModuleScope(module->name, resumeScope);
|
prepareModuleScope(module->name, resumeScope);
|
||||||
|
|
||||||
program->visit(&gp);
|
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)
|
void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
|
||||||
|
@ -3895,6 +4004,13 @@ void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, As
|
||||||
prepareModuleScope(module->name, globalScope);
|
prepareModuleScope(module->name, globalScope);
|
||||||
|
|
||||||
program->visit(&gp);
|
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)
|
bool ConstraintGenerator::recordPropertyAssignment(TypeId ty)
|
||||||
|
|
|
@ -1168,6 +1168,8 @@ void DataFlowGraphBuilder::visitType(AstType* t)
|
||||||
return visitType(f);
|
return visitType(f);
|
||||||
else if (auto tyof = t->as<AstTypeTypeof>())
|
else if (auto tyof = t->as<AstTypeTypeof>())
|
||||||
return visitType(tyof);
|
return visitType(tyof);
|
||||||
|
else if (auto o = t->as<AstTypeOptional>())
|
||||||
|
return;
|
||||||
else if (auto u = t->as<AstTypeUnion>())
|
else if (auto u = t->as<AstTypeUnion>())
|
||||||
return visitType(u);
|
return visitType(u);
|
||||||
else if (auto i = t->as<AstTypeIntersection>())
|
else if (auto i = t->as<AstTypeIntersection>())
|
||||||
|
|
|
@ -317,4 +317,83 @@ std::string getBuiltinDefinitionSource()
|
||||||
return result;
|
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
|
} // namespace Luau
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/TypeChecker2.h"
|
||||||
#include "Luau/TypeFunction.h"
|
#include "Luau/TypeFunction.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
||||||
|
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix)
|
||||||
|
|
||||||
static std::string wrongNumberOfArgsString(
|
static std::string wrongNumberOfArgsString(
|
||||||
size_t expectedCount,
|
size_t expectedCount,
|
||||||
|
@ -116,7 +118,10 @@ struct ErrorConverter
|
||||||
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
|
size_t luauIndentTypeMismatchMaxTypeLength = size_t(FInt::LuauIndentTypeMismatchMaxTypeLength);
|
||||||
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
|
if (givenType.length() <= luauIndentTypeMismatchMaxTypeLength || wantedType.length() <= luauIndentTypeMismatchMaxTypeLength)
|
||||||
return "Type " + given + " could not be converted into " + wanted;
|
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)
|
if (givenTypeName == wantedTypeName)
|
||||||
|
@ -751,8 +756,15 @@ struct ErrorConverter
|
||||||
|
|
||||||
std::string operator()(const NonStrictFunctionDefinitionError& e) const
|
std::string operator()(const NonStrictFunctionDefinitionError& e) const
|
||||||
{
|
{
|
||||||
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
|
if (FFlag::LuauNonStrictFuncDefErrorFix && e.functionName.empty())
|
||||||
"' is used in a way that will run time error";
|
{
|
||||||
|
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
|
std::string operator()(const PropertyAccessViolation& e) const
|
||||||
|
|
|
@ -39,6 +39,9 @@ LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
|
||||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
|
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
|
LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||||
|
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
|
||||||
|
|
||||||
namespace
|
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
|
struct MixedModeIncrementalTCDefFinder : public AstVisitor
|
||||||
{
|
{
|
||||||
|
|
||||||
bool visit(AstExprLocal* local) override
|
bool visit(AstExprLocal* local) override
|
||||||
{
|
{
|
||||||
referencedLocalDefs.emplace_back(local->local, local);
|
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);
|
reporter->reportFragmentString(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
FragmentTypeCheckResult typecheckFragment_(
|
FragmentTypeCheckResult typecheckFragmentHelper_DEPRECATED(
|
||||||
Frontend& frontend,
|
Frontend& frontend,
|
||||||
AstStatBlock* root,
|
AstStatBlock* root,
|
||||||
const ModulePtr& stale,
|
const ModulePtr& stale,
|
||||||
|
@ -692,6 +838,7 @@ FragmentTypeCheckResult typecheckFragment_(
|
||||||
frontend.builtinTypes,
|
frontend.builtinTypes,
|
||||||
iceHandler,
|
iceHandler,
|
||||||
stale->getModuleScope(),
|
stale->getModuleScope(),
|
||||||
|
frontend.globals.globalTypeFunctionScope,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
NotNull{&dfg},
|
NotNull{&dfg},
|
||||||
|
@ -729,7 +876,7 @@ FragmentTypeCheckResult typecheckFragment_(
|
||||||
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope);
|
||||||
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
|
mixedModeCompatibility(closestScope, freshChildOfNearestScope, stale, NotNull{&dfg}, root);
|
||||||
// closest Scope -> children = { ...., freshChildOfNearestScope}
|
// 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());
|
closestScope->children.emplace_back(freshChildOfNearestScope.get());
|
||||||
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
cg.visitFragmentRoot(freshChildOfNearestScope, root);
|
||||||
// Trim nearestChild from the closestScope
|
// Trim nearestChild from the closestScope
|
||||||
|
@ -788,6 +935,149 @@ FragmentTypeCheckResult typecheckFragment_(
|
||||||
return {std::move(incrementalModule), std::move(freshChildOfNearestScope)};
|
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(
|
std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
||||||
Frontend& frontend,
|
Frontend& frontend,
|
||||||
|
@ -850,7 +1140,11 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
||||||
FrontendOptions frontendOptions = opts.value_or(frontend.options);
|
FrontendOptions frontendOptions = opts.value_or(frontend.options);
|
||||||
const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement);
|
const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement);
|
||||||
FragmentTypeCheckResult result =
|
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);
|
result.ancestry = std::move(parseResult.ancestry);
|
||||||
reportFragmentString(reporter, tryParse->fragmentToParse);
|
reportFragmentString(reporter, tryParse->fragmentToParse);
|
||||||
return {FragmentTypeCheckStatus::Success, result};
|
return {FragmentTypeCheckStatus::Success, result};
|
||||||
|
|
|
@ -1532,6 +1532,7 @@ ModulePtr check(
|
||||||
NotNull<ModuleResolver> moduleResolver,
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
NotNull<FileResolver> fileResolver,
|
NotNull<FileResolver> fileResolver,
|
||||||
const ScopePtr& parentScope,
|
const ScopePtr& parentScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
FrontendOptions options,
|
FrontendOptions options,
|
||||||
TypeCheckLimits limits,
|
TypeCheckLimits limits,
|
||||||
|
@ -1548,6 +1549,7 @@ ModulePtr check(
|
||||||
moduleResolver,
|
moduleResolver,
|
||||||
fileResolver,
|
fileResolver,
|
||||||
parentScope,
|
parentScope,
|
||||||
|
typeFunctionScope,
|
||||||
std::move(prepareModuleScope),
|
std::move(prepareModuleScope),
|
||||||
options,
|
options,
|
||||||
limits,
|
limits,
|
||||||
|
@ -1609,6 +1611,7 @@ ModulePtr check(
|
||||||
NotNull<ModuleResolver> moduleResolver,
|
NotNull<ModuleResolver> moduleResolver,
|
||||||
NotNull<FileResolver> fileResolver,
|
NotNull<FileResolver> fileResolver,
|
||||||
const ScopePtr& parentScope,
|
const ScopePtr& parentScope,
|
||||||
|
const ScopePtr& typeFunctionScope,
|
||||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||||
FrontendOptions options,
|
FrontendOptions options,
|
||||||
TypeCheckLimits limits,
|
TypeCheckLimits limits,
|
||||||
|
@ -1666,6 +1669,7 @@ ModulePtr check(
|
||||||
builtinTypes,
|
builtinTypes,
|
||||||
iceHandler,
|
iceHandler,
|
||||||
parentScope,
|
parentScope,
|
||||||
|
typeFunctionScope,
|
||||||
std::move(prepareModuleScope),
|
std::move(prepareModuleScope),
|
||||||
logger.get(),
|
logger.get(),
|
||||||
NotNull{&dfg},
|
NotNull{&dfg},
|
||||||
|
@ -1848,6 +1852,7 @@ ModulePtr Frontend::check(
|
||||||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
|
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver},
|
||||||
NotNull{fileResolver},
|
NotNull{fileResolver},
|
||||||
environmentScope ? *environmentScope : globals.globalScope,
|
environmentScope ? *environmentScope : globals.globalScope,
|
||||||
|
globals.globalTypeFunctionScope,
|
||||||
prepareModuleScopeWrap,
|
prepareModuleScopeWrap,
|
||||||
options,
|
options,
|
||||||
typeCheckLimits,
|
typeCheckLimits,
|
||||||
|
|
|
@ -359,7 +359,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
DenseHashSet<const void*> seenPositive{nullptr};
|
DenseHashSet<const void*> seenPositive{nullptr};
|
||||||
DenseHashSet<const void*> seenNegative{nullptr};
|
DenseHashSet<const void*> seenNegative{nullptr};
|
||||||
|
|
||||||
bool seenWithPolarity(const void* ty)
|
bool seenWithCurrentPolarity(const void* ty)
|
||||||
{
|
{
|
||||||
switch (polarity)
|
switch (polarity)
|
||||||
{
|
{
|
||||||
|
@ -401,7 +401,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypeId ty) override
|
bool visit(TypeId ty) override
|
||||||
{
|
{
|
||||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
LUAU_ASSERT(ty);
|
LUAU_ASSERT(ty);
|
||||||
|
@ -410,7 +410,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypeId ty, const FreeType& ft) override
|
bool visit(TypeId ty, const FreeType& ft) override
|
||||||
{
|
{
|
||||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!subsumes(scope, ft.scope))
|
if (!subsumes(scope, ft.scope))
|
||||||
|
@ -435,7 +435,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypeId ty, const TableType& tt) override
|
bool visit(TypeId ty, const TableType& tt) override
|
||||||
{
|
{
|
||||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
|
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
|
bool visit(TypeId ty, const FunctionType& ft) override
|
||||||
{
|
{
|
||||||
if (cachedTypes->contains(ty) || seenWithPolarity(ty))
|
if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
flip();
|
flip();
|
||||||
|
@ -500,7 +500,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||||
{
|
{
|
||||||
if (seenWithPolarity(tp))
|
if (seenWithCurrentPolarity(tp))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!subsumes(scope, ftp.scope))
|
if (!subsumes(scope, ftp.scope))
|
||||||
|
@ -547,7 +547,7 @@ struct TypeCacher : TypeOnceVisitor
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void cache(TypeId ty)
|
void cache(TypeId ty) const
|
||||||
{
|
{
|
||||||
cachedTypes->insert(ty);
|
cachedTypes->insert(ty);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
|
||||||
: builtinTypes(builtinTypes)
|
: builtinTypes(builtinTypes)
|
||||||
{
|
{
|
||||||
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
|
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("any", TypeFun{{}, builtinTypes->anyType});
|
||||||
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});
|
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
|
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
|
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauNonStrictFuncDefErrorFix)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -763,7 +764,17 @@ struct NonStrictTypeChecker
|
||||||
for (AstLocal* local : exprFn->args)
|
for (AstLocal* local : exprFn->args)
|
||||||
{
|
{
|
||||||
if (std::optional<TypeId> ty = willRunTimeErrorFunctionDefinition(local, remainder))
|
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));
|
remainder.remove(dfg->getDef(local));
|
||||||
}
|
}
|
||||||
return remainder;
|
return remainder;
|
||||||
|
|
|
@ -22,7 +22,7 @@ LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegationFix)
|
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegationFix)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFixNormalizedIntersectionOfNegatedClass)
|
LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -304,7 +304,9 @@ bool NormalizedType::isUnknown() const
|
||||||
|
|
||||||
// Otherwise, we can still be unknown!
|
// Otherwise, we can still be unknown!
|
||||||
bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
|
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
|
// Check is class
|
||||||
bool isTopClass = false;
|
bool isTopClass = false;
|
||||||
|
@ -2289,7 +2291,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
||||||
|
|
||||||
for (auto nIt = negations.begin(); nIt != negations.end();)
|
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
|
// Hitting this block means that the incoming class is a
|
||||||
// subclass of this type, _and_ one of its negations is a
|
// subclass of this type, _and_ one of its negations is a
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
#include "Luau/Ast.h"
|
#include "Luau/Ast.h"
|
||||||
#include "Luau/Module.h"
|
#include "Luau/Module.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauExtendedSimpleRequire)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -106,96 +104,50 @@ struct RequireTracer : AstVisitor
|
||||||
{
|
{
|
||||||
ModuleInfo moduleContext{currentModuleName};
|
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
|
if (AstNode* dep = getDependent(work[i]))
|
||||||
work.reserve(requireCalls.size());
|
work.push_back(dep);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// resolve all expressions to a module info
|
||||||
|
for (size_t i = work.size(); i > 0; --i)
|
||||||
{
|
{
|
||||||
// seed worklist with require arguments
|
AstNode* expr = work[i - 1];
|
||||||
work_DEPRECATED.reserve(requireCalls.size());
|
|
||||||
|
|
||||||
for (AstExprCall* require : requireCalls)
|
// when multiple expressions depend on the same one we push it to work queue multiple times
|
||||||
work_DEPRECATED.push_back(require->args.data[0]);
|
if (result.exprs.contains(expr))
|
||||||
|
continue;
|
||||||
|
|
||||||
// push all dependent expressions to the work stack; note that the vector is modified during traversal
|
std::optional<ModuleInfo> info;
|
||||||
for (size_t i = 0; i < work_DEPRECATED.size(); ++i)
|
|
||||||
if (AstExpr* dep = getDependent_DEPRECATED(work_DEPRECATED[i]))
|
|
||||||
work_DEPRECATED.push_back(dep);
|
|
||||||
|
|
||||||
// resolve all expressions to a module info
|
if (AstNode* dep = getDependent(expr))
|
||||||
for (size_t i = work_DEPRECATED.size(); i > 0; --i)
|
|
||||||
{
|
{
|
||||||
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 (context && expr->is<AstExprLocal>())
|
||||||
if (result.exprs.contains(expr))
|
info = *context; // locals just inherit their dependent context, no resolution required
|
||||||
continue;
|
else if (context && (expr->is<AstExprGroup>() || expr->is<AstTypeGroup>()))
|
||||||
|
info = *context; // simple group nodes propagate their value
|
||||||
std::optional<ModuleInfo> info;
|
else if (context && (expr->is<AstTypeTypeof>() || expr->is<AstExprTypeAssertion>()))
|
||||||
|
info = *context; // typeof type annotations will resolve to the typeof content
|
||||||
if (AstExpr* dep = getDependent_DEPRECATED(expr))
|
else if (AstExpr* asExpr = expr->asExpr())
|
||||||
{
|
info = fileResolver->resolveModule(context, asExpr);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
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
|
// resolve all requires according to their argument
|
||||||
|
@ -224,7 +176,6 @@ struct RequireTracer : AstVisitor
|
||||||
ModuleName currentModuleName;
|
ModuleName currentModuleName;
|
||||||
|
|
||||||
DenseHashMap<AstLocal*, AstExpr*> locals;
|
DenseHashMap<AstLocal*, AstExpr*> locals;
|
||||||
std::vector<AstExpr*> work_DEPRECATED;
|
|
||||||
std::vector<AstNode*> work;
|
std::vector<AstNode*> work;
|
||||||
std::vector<AstExprCall*> requireCalls;
|
std::vector<AstExprCall*> requireCalls;
|
||||||
};
|
};
|
||||||
|
|
|
@ -84,6 +84,17 @@ std::optional<TypeId> Scope::lookupUnrefinedType(DefId def) const
|
||||||
return std::nullopt;
|
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
|
std::optional<TypeId> Scope::lookup(DefId def) const
|
||||||
{
|
{
|
||||||
for (const Scope* current = this; current; current = current->parent.get())
|
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;
|
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`.
|
// Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`.
|
||||||
void Scope::inheritAssignments(const ScopePtr& childScope)
|
void Scope::inheritAssignments(const ScopePtr& childScope)
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingFixTailPack)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -754,7 +753,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
// Match head types pairwise
|
// Match head types pairwise
|
||||||
|
|
||||||
for (size_t i = 0; i < headSize; ++i)
|
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
|
// Handle mismatched head sizes
|
||||||
|
|
||||||
|
@ -767,7 +767,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
for (size_t i = headSize; i < superHead.size(); ++i)
|
for (size_t i = headSize; i < superHead.size(); ++i)
|
||||||
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
|
results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope)
|
||||||
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
|
.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))
|
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)
|
for (size_t i = headSize; i < subHead.size(); ++i)
|
||||||
results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope)
|
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()));
|
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
|
||||||
}
|
}
|
||||||
else if (auto gt = get<GenericTypePack>(*superTail))
|
else if (auto gt = get<GenericTypePack>(*superTail))
|
||||||
|
@ -859,7 +859,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
else
|
else
|
||||||
return SubtypingResult{false}
|
return SubtypingResult{false}
|
||||||
.withSuperComponent(TypePath::PackField::Tail)
|
.withSuperComponent(TypePath::PackField::Tail)
|
||||||
.withError({scope->location, UnexpectedTypePackInSubtyping{FFlag::LuauSubtypingFixTailPack ? *superTail : *subTail}});
|
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return {false};
|
return {false};
|
||||||
|
@ -1100,7 +1100,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (TypeId ty : subUnion)
|
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);
|
return SubtypingResult::all(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1110,7 +1110,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (TypeId ty : superIntersection)
|
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);
|
return SubtypingResult::all(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1120,7 +1120,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (TypeId ty : subIntersection)
|
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);
|
return SubtypingResult::any(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,6 @@
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Unifier2.h"
|
#include "Luau/Unifier2.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDontInPlaceMutateTableType)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAllowNonSharedTableTypesInLiteral)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
|
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -277,19 +275,11 @@ TypeId matchLiteralType(
|
||||||
|
|
||||||
Property& prop = it->second;
|
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
|
||||||
// If we encounter a duplcate property, we may have already
|
// that will definitely crash is trying to access a write
|
||||||
// set it to be read-only. If that's the case, the only thing
|
// only property.
|
||||||
// that will definitely crash is trying to access a write
|
LUAU_ASSERT(!prop.isWriteOnly());
|
||||||
// only property.
|
|
||||||
LUAU_ASSERT(!prop.isWriteOnly());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Table literals always initially result in shared read-write types
|
|
||||||
LUAU_ASSERT(prop.isShared());
|
|
||||||
}
|
|
||||||
TypeId propTy = *prop.readTy;
|
TypeId propTy = *prop.readTy;
|
||||||
|
|
||||||
auto it2 = expectedTableTy->props.find(keyStr);
|
auto it2 = expectedTableTy->props.find(keyStr);
|
||||||
|
@ -322,10 +312,7 @@ TypeId matchLiteralType(
|
||||||
else
|
else
|
||||||
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
|
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
|
||||||
|
|
||||||
if (FFlag::LuauDontInPlaceMutateTableType)
|
keysToDelete.insert(item.key->as<AstExprConstantString>());
|
||||||
keysToDelete.insert(item.key->as<AstExprConstantString>());
|
|
||||||
else
|
|
||||||
tableTy->props.erase(keyStr);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,14 +421,11 @@ TypeId matchLiteralType(
|
||||||
LUAU_ASSERT(!"Unexpected");
|
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};
|
||||||
const AstArray<char>& s = key->value;
|
tableTy->props.erase(keyStr);
|
||||||
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
|
// Keys that the expectedType says we should have, but that aren't
|
||||||
|
|
|
@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauStoreCSTData)
|
||||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||||
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
||||||
|
LUAU_FASTFLAG(LuauParseOptionalAsNode)
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -271,6 +272,43 @@ private:
|
||||||
const Position* commaPosition;
|
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
|
struct Printer_DEPRECATED
|
||||||
{
|
{
|
||||||
explicit Printer_DEPRECATED(Writer& writer)
|
explicit Printer_DEPRECATED(Writer& writer)
|
||||||
|
@ -1216,6 +1254,15 @@ struct Printer_DEPRECATED
|
||||||
|
|
||||||
for (size_t i = 0; i < a->types.size; ++i)
|
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)
|
if (i > 0)
|
||||||
{
|
{
|
||||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
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);
|
advance(annotation.location.begin);
|
||||||
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
|
if (const AstTypePackVariadic* variadicTp = annotation.as<AstTypePackVariadic>())
|
||||||
|
@ -1322,15 +1369,22 @@ struct Printer
|
||||||
|
|
||||||
visualizeTypeAnnotation(*variadicTp->variadicType);
|
visualizeTypeAnnotation(*variadicTp->variadicType);
|
||||||
}
|
}
|
||||||
else if (const AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
|
else if (AstTypePackGeneric* genericTp = annotation.as<AstTypePackGeneric>())
|
||||||
{
|
{
|
||||||
writer.symbol(genericTp->genericName.value);
|
writer.symbol(genericTp->genericName.value);
|
||||||
|
if (const auto cstNode = lookupCstNode<CstTypePackGeneric>(genericTp))
|
||||||
|
advance(cstNode->ellipsisPosition);
|
||||||
writer.symbol("...");
|
writer.symbol("...");
|
||||||
}
|
}
|
||||||
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
|
else if (AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!forVarArg);
|
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
|
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);
|
size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0);
|
||||||
if (typeCount == 0)
|
if (typeCount == 0)
|
||||||
{
|
{
|
||||||
|
if (openParenthesesPosition)
|
||||||
|
advance(*openParenthesesPosition);
|
||||||
writer.symbol("(");
|
writer.symbol("(");
|
||||||
|
if (closeParenthesesPosition)
|
||||||
|
advance(*closeParenthesesPosition);
|
||||||
writer.symbol(")");
|
writer.symbol(")");
|
||||||
}
|
}
|
||||||
else if (typeCount == 1)
|
else if (typeCount == 1)
|
||||||
{
|
{
|
||||||
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
|
bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is<AstTypeGroup>());
|
||||||
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
||||||
|
{
|
||||||
|
if (openParenthesesPosition)
|
||||||
|
advance(*openParenthesesPosition);
|
||||||
writer.symbol("(");
|
writer.symbol("(");
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgNameInserter(writer, argNames, argNamesColonPositions)();
|
||||||
|
|
||||||
// Only variadic tail
|
// Only variadic tail
|
||||||
if (list.types.size == 0)
|
if (list.types.size == 0)
|
||||||
|
@ -1363,33 +1435,50 @@ struct Printer
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
if (FFlag::LuauAstTypeGroup3 ? shouldParenthesize : unconditionallyParenthesize)
|
||||||
|
{
|
||||||
|
if (closeParenthesesPosition)
|
||||||
|
advance(*closeParenthesesPosition);
|
||||||
writer.symbol(")");
|
writer.symbol(")");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (openParenthesesPosition)
|
||||||
|
advance(*openParenthesesPosition);
|
||||||
writer.symbol("(");
|
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)
|
for (const auto& el : list.types)
|
||||||
{
|
{
|
||||||
if (first)
|
comma();
|
||||||
first = false;
|
argName();
|
||||||
else
|
|
||||||
writer.symbol(",");
|
|
||||||
|
|
||||||
visualizeTypeAnnotation(*el);
|
visualizeTypeAnnotation(*el);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.tailType)
|
if (list.tailType)
|
||||||
{
|
{
|
||||||
writer.symbol(",");
|
comma();
|
||||||
visualizeTypePackAnnotation(*list.tailType, false);
|
visualizeTypePackAnnotation(*list.tailType, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (closeParenthesesPosition)
|
||||||
|
advance(*closeParenthesesPosition);
|
||||||
writer.symbol(")");
|
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)
|
bool isIntegerish(double d)
|
||||||
{
|
{
|
||||||
if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min())
|
if (d <= std::numeric_limits<int>::max() && d >= std::numeric_limits<int>::min())
|
||||||
|
@ -1406,7 +1495,7 @@ struct Printer
|
||||||
{
|
{
|
||||||
writer.symbol("(");
|
writer.symbol("(");
|
||||||
visualize(*a->expr);
|
visualize(*a->expr);
|
||||||
advance(Position{a->location.end.line, a->location.end.column - 1});
|
advanceBefore(a->location.end, 1);
|
||||||
writer.symbol(")");
|
writer.symbol(")");
|
||||||
}
|
}
|
||||||
else if (expr.is<AstExprConstantNil>())
|
else if (expr.is<AstExprConstantNil>())
|
||||||
|
@ -1775,6 +1864,14 @@ struct Printer
|
||||||
writer.advance(newPos);
|
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)
|
void visualize(AstStat& program)
|
||||||
{
|
{
|
||||||
advance(program.location.begin);
|
advance(program.location.begin);
|
||||||
|
@ -1817,8 +1914,8 @@ struct Printer
|
||||||
visualizeBlock(*a->body);
|
visualizeBlock(*a->body);
|
||||||
if (const auto cstNode = lookupCstNode<CstStatRepeat>(a))
|
if (const auto cstNode = lookupCstNode<CstStatRepeat>(a))
|
||||||
writer.advance(cstNode->untilPosition);
|
writer.advance(cstNode->untilPosition);
|
||||||
else if (a->condition->location.begin.column > 5)
|
else
|
||||||
writer.advance(Position{a->condition->location.begin.line, a->condition->location.begin.column - 6});
|
advanceBefore(a->condition->location.begin, 6);
|
||||||
writer.keyword("until");
|
writer.keyword("until");
|
||||||
visualize(*a->condition);
|
visualize(*a->condition);
|
||||||
}
|
}
|
||||||
|
@ -2121,7 +2218,20 @@ struct Printer
|
||||||
{
|
{
|
||||||
if (writeTypes)
|
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);
|
writer.identifier(t->name.value);
|
||||||
visualizeFunctionBody(*t->body);
|
visualizeFunctionBody(*t->body);
|
||||||
}
|
}
|
||||||
|
@ -2152,16 +2262,22 @@ struct Printer
|
||||||
if (program.hasSemicolon)
|
if (program.hasSemicolon)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauStoreCSTData)
|
if (FFlag::LuauStoreCSTData)
|
||||||
advance(Position{program.location.end.line, program.location.end.column - 1});
|
advanceBefore(program.location.end, 1);
|
||||||
writer.symbol(";");
|
writer.symbol(";");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void visualizeFunctionBody(AstExprFunction& func)
|
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)
|
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("<");
|
writer.symbol("<");
|
||||||
for (const auto& o : func.generics)
|
for (const auto& o : func.generics)
|
||||||
{
|
{
|
||||||
|
@ -2176,13 +2292,19 @@ struct Printer
|
||||||
|
|
||||||
writer.advance(o->location.begin);
|
writer.advance(o->location.begin);
|
||||||
writer.identifier(o->name.value);
|
writer.identifier(o->name.value);
|
||||||
|
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
|
||||||
|
advance(genericTypePackCstNode->ellipsisPosition);
|
||||||
writer.symbol("...");
|
writer.symbol("...");
|
||||||
}
|
}
|
||||||
|
if (cstNode)
|
||||||
|
advance(cstNode->closeGenericsPosition);
|
||||||
writer.symbol(">");
|
writer.symbol(">");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (func.argLocation)
|
||||||
|
advance(func.argLocation->begin);
|
||||||
writer.symbol("(");
|
writer.symbol("(");
|
||||||
CommaSeparatorInserter comma(writer);
|
CommaSeparatorInserter comma(writer, cstNode ? cstNode->argsCommaPositions.begin() : nullptr);
|
||||||
|
|
||||||
for (size_t i = 0; i < func.args.size; ++i)
|
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(")");
|
writer.symbol(")");
|
||||||
|
|
||||||
if (writeTypes && func.returnAnnotation)
|
if (writeTypes && func.returnAnnotation)
|
||||||
{
|
{
|
||||||
|
if (cstNode)
|
||||||
|
advance(cstNode->returnSpecifierPosition);
|
||||||
writer.symbol(":");
|
writer.symbol(":");
|
||||||
writer.space();
|
writer.space();
|
||||||
|
|
||||||
|
@ -2340,9 +2466,13 @@ struct Printer
|
||||||
}
|
}
|
||||||
else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
|
else if (const auto& a = typeAnnotation.as<AstTypeFunction>())
|
||||||
{
|
{
|
||||||
|
const auto cstNode = lookupCstNode<CstTypeFunction>(a);
|
||||||
|
|
||||||
if (a->generics.size > 0 || a->genericPacks.size > 0)
|
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("<");
|
writer.symbol("<");
|
||||||
for (const auto& o : a->generics)
|
for (const auto& o : a->generics)
|
||||||
{
|
{
|
||||||
|
@ -2357,15 +2487,29 @@ struct Printer
|
||||||
|
|
||||||
writer.advance(o->location.begin);
|
writer.advance(o->location.begin);
|
||||||
writer.identifier(o->name.value);
|
writer.identifier(o->name.value);
|
||||||
|
if (const auto* genericTypePackCstNode = lookupCstNode<CstGenericTypePack>(o))
|
||||||
|
advance(genericTypePackCstNode->ellipsisPosition);
|
||||||
writer.symbol("...");
|
writer.symbol("...");
|
||||||
}
|
}
|
||||||
|
if (cstNode)
|
||||||
|
advance(cstNode->closeGenericsPosition);
|
||||||
writer.symbol(">");
|
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("->");
|
writer.symbol("->");
|
||||||
visualizeTypeList(a->returnTypes, true);
|
visualizeTypeList(a->returnTypes, true);
|
||||||
}
|
}
|
||||||
|
@ -2557,6 +2701,15 @@ struct Printer
|
||||||
|
|
||||||
for (size_t i = 0; i < a->types.size; ++i)
|
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)
|
if (i > 0)
|
||||||
{
|
{
|
||||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||||
|
@ -2599,7 +2752,7 @@ struct Printer
|
||||||
{
|
{
|
||||||
writer.symbol("(");
|
writer.symbol("(");
|
||||||
visualizeTypeAnnotation(*a->type);
|
visualizeTypeAnnotation(*a->type);
|
||||||
advance(Position{a->location.end.line, a->location.end.column - 1});
|
advanceBefore(a->location.end, 1);
|
||||||
writer.symbol(")");
|
writer.symbol(")");
|
||||||
}
|
}
|
||||||
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
|
else if (const auto& a = typeAnnotation.as<AstTypeSingletonBool>())
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||||
|
|
||||||
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
|
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
|
||||||
{
|
{
|
||||||
char* result = (char*)allocator.allocate(contents.size() + 1);
|
char* result = (char*)allocator.allocate(contents.size() + 1);
|
||||||
|
@ -305,7 +307,8 @@ public:
|
||||||
std::optional<AstArgumentName>* arg = &argNames.data[i++];
|
std::optional<AstArgumentName>* arg = &argNames.data[i++];
|
||||||
|
|
||||||
if (el)
|
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
|
else
|
||||||
new (arg) std::optional<AstArgumentName>();
|
new (arg) std::optional<AstArgumentName>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,13 @@
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
|
||||||
|
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -1201,7 +1204,8 @@ void TypeChecker2::visit(AstStatTypeAlias* stat)
|
||||||
|
|
||||||
void TypeChecker2::visit(AstStatTypeFunction* 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)
|
void TypeChecker2::visit(AstTypeList types)
|
||||||
|
@ -2701,20 +2705,61 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
|
||||||
if (!subLeafTy && !superLeafTy && !subLeafTp && !superLeafTp)
|
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);
|
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 (FFlag::LuauImproveTypePathsInErrors)
|
||||||
if (reasoning.variance == SubtypingVariance::Invariant)
|
{
|
||||||
relation = "exactly";
|
std::string relation = "a subtype of";
|
||||||
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
if (reasoning.variance == SubtypingVariance::Invariant)
|
||||||
relation = "a supertype of";
|
relation = "exactly";
|
||||||
|
else if (reasoning.variance == SubtypingVariance::Contravariant)
|
||||||
|
relation = "a supertype of";
|
||||||
|
|
||||||
std::string reason;
|
std::string subLeafAsString = toString(subLeaf);
|
||||||
if (reasoning.subPath == reasoning.superPath)
|
// if the string is empty, it must be an empty type pack
|
||||||
reason = "at " + toString(reasoning.subPath) + ", " + toString(subLeaf) + " is not " + relation + " " + toString(superLeaf);
|
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
|
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 we haven't already proved this isn't suppressing, we have to keep checking.
|
||||||
if (suppressed)
|
if (suppressed)
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/TypeChecker2.h"
|
||||||
#include "Luau/TypeFunctionReductionGuesser.h"
|
#include "Luau/TypeFunctionReductionGuesser.h"
|
||||||
#include "Luau/TypeFunctionRuntime.h"
|
#include "Luau/TypeFunctionRuntime.h"
|
||||||
#include "Luau/TypeFunctionRuntimeBuilder.h"
|
#include "Luau/TypeFunctionRuntimeBuilder.h"
|
||||||
|
@ -49,12 +50,13 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
|
||||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
|
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
|
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDoNotGeneralizeInTypeFunctions)
|
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauPreventReentrantTypeFunctionReduction)
|
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
|
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
|
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
|
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
|
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
|
||||||
|
|
||||||
namespace Luau
|
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};
|
TypeFunctionReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force};
|
||||||
int iterationCount = 0;
|
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,
|
reducer.step();
|
||||||
// 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};
|
++iterationCount;
|
||||||
while (!reducer.done())
|
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
|
||||||
{
|
{
|
||||||
reducer.step();
|
reducer.result.errors.emplace_back(location, CodeTooComplex{});
|
||||||
|
break;
|
||||||
++iterationCount;
|
|
||||||
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
|
|
||||||
{
|
|
||||||
reducer.result.errors.emplace_back(location, CodeTooComplex{});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::move(reducer.result);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
while (!reducer.done())
|
|
||||||
{
|
|
||||||
reducer.step();
|
|
||||||
|
|
||||||
++iterationCount;
|
return std::move(reducer.result);
|
||||||
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
|
|
||||||
{
|
|
||||||
reducer.result.errors.emplace_back(location, CodeTooComplex{});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::move(reducer.result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
|
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
|
||||||
|
@ -861,15 +844,6 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
||||||
if (isPending(operandTy, ctx->solver))
|
if (isPending(operandTy, ctx->solver))
|
||||||
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
||||||
|
|
||||||
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
||||||
if (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);
|
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
|
||||||
NormalizationResult inhabited = ctx->normalizer->isInhabited(normTy.get());
|
NormalizationResult inhabited = ctx->normalizer->isInhabited(normTy.get());
|
||||||
|
|
||||||
|
@ -953,15 +927,6 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
||||||
if (isPending(operandTy, ctx->solver))
|
if (isPending(operandTy, ctx->solver))
|
||||||
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
|
||||||
|
|
||||||
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
||||||
if (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);
|
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.
|
// 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))
|
else if (isPending(rhsTy, ctx->solver))
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
||||||
if (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`.
|
// 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> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||||
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||||
|
@ -1433,21 +1383,6 @@ TypeFunctionReductionResult<TypeId> concatTypeFunction(
|
||||||
else if (isPending(rhsTy, ctx->solver))
|
else if (isPending(rhsTy, ctx->solver))
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
||||||
if (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> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||||
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||||
|
|
||||||
|
@ -1548,21 +1483,6 @@ TypeFunctionReductionResult<TypeId> andTypeFunction(
|
||||||
else if (isPending(rhsTy, ctx->solver))
|
else if (isPending(rhsTy, ctx->solver))
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
||||||
if (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.
|
// 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 filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->falsyType);
|
||||||
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
|
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
|
||||||
|
@ -1603,21 +1523,6 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
|
||||||
else if (isPending(rhsTy, ctx->solver))
|
else if (isPending(rhsTy, ctx->solver))
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
||||||
if (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.
|
// 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 filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
|
||||||
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
|
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
|
||||||
|
@ -1689,21 +1594,6 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
|
||||||
lhsTy = follow(lhsTy);
|
lhsTy = follow(lhsTy);
|
||||||
rhsTy = follow(rhsTy);
|
rhsTy = follow(rhsTy);
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
||||||
if (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
|
// 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);
|
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||||
|
@ -1827,21 +1717,6 @@ TypeFunctionReductionResult<TypeId> eqTypeFunction(
|
||||||
else if (isPending(rhsTy, ctx->solver))
|
else if (isPending(rhsTy, ctx->solver))
|
||||||
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
|
||||||
|
|
||||||
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
||||||
if (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> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||||
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||||
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
|
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>>
|
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
|
||||||
{
|
{
|
||||||
std::vector<TypeId> toBlock;
|
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
|
// we need a more complex check for blocking on the discriminant in particular
|
||||||
FindRefinementBlockers frb;
|
FindRefinementBlockers frb;
|
||||||
frb.traverse(discriminant);
|
frb.traverse(discriminant);
|
||||||
|
@ -2131,15 +1992,6 @@ TypeFunctionReductionResult<TypeId> singletonTypeFunction(
|
||||||
if (isPending(type, ctx->solver))
|
if (isPending(type, ctx->solver))
|
||||||
return {std::nullopt, Reduction::MaybeOk, {type}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {type}, {}};
|
||||||
|
|
||||||
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
||||||
if (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;
|
TypeId followed = type;
|
||||||
// we want to follow through a negation here as well.
|
// we want to follow through a negation here as well.
|
||||||
if (auto negation = get<NegationType>(followed))
|
if (auto negation = get<NegationType>(followed))
|
||||||
|
@ -2207,73 +2059,24 @@ TypeFunctionReductionResult<TypeId> unionTypeFunction(
|
||||||
if (typeParams.size() == 1)
|
if (typeParams.size() == 1)
|
||||||
return {follow(typeParams[0]), Reduction::MaybeOk, {}, {}};
|
return {follow(typeParams[0]), Reduction::MaybeOk, {}, {}};
|
||||||
|
|
||||||
if (FFlag::LuauClipNestedAndRecursiveUnion)
|
|
||||||
|
CollectUnionTypeOptions collector{ctx};
|
||||||
|
collector.traverse(instance);
|
||||||
|
|
||||||
|
if (!collector.blockingTypes.empty())
|
||||||
{
|
{
|
||||||
|
std::vector<TypeId> blockingTypes{collector.blockingTypes.begin(), collector.blockingTypes.end()};
|
||||||
CollectUnionTypeOptions collector{ctx};
|
return {std::nullopt, Reduction::MaybeOk, std::move(blockingTypes), {}};
|
||||||
collector.traverse(instance);
|
|
||||||
|
|
||||||
if (!collector.blockingTypes.empty())
|
|
||||||
{
|
|
||||||
std::vector<TypeId> blockingTypes{collector.blockingTypes.begin(), collector.blockingTypes.end()};
|
|
||||||
return {std::nullopt, Reduction::MaybeOk, std::move(blockingTypes), {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeId resultTy = ctx->builtins->neverType;
|
|
||||||
for (auto ty : collector.options)
|
|
||||||
{
|
|
||||||
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
|
|
||||||
// This condition might fire if one of the arguments to this type
|
|
||||||
// function is a free type somewhere deep in a nested union or
|
|
||||||
// intersection type, even though we ran a pass above to capture
|
|
||||||
// some blocked types.
|
|
||||||
if (!result.blockedTypes.empty())
|
|
||||||
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
|
||||||
|
|
||||||
resultTy = result.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {resultTy, Reduction::MaybeOk, {}, {}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to follow all of the type parameters.
|
|
||||||
std::vector<TypeId> types;
|
|
||||||
types.reserve(typeParams.size());
|
|
||||||
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;
|
TypeId resultTy = ctx->builtins->neverType;
|
||||||
for (auto ty : types)
|
for (auto ty : collector.options)
|
||||||
{
|
{
|
||||||
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
|
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())
|
if (!result.blockedTypes.empty())
|
||||||
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
||||||
|
|
||||||
|
@ -2281,6 +2084,7 @@ TypeFunctionReductionResult<TypeId> unionTypeFunction(
|
||||||
}
|
}
|
||||||
|
|
||||||
return {resultTy, Reduction::MaybeOk, {}, {}};
|
return {resultTy, Reduction::MaybeOk, {}, {}};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2621,7 +2425,12 @@ bool searchPropsAndIndexer(
|
||||||
if (auto propUnionTy = get<UnionType>(propTy))
|
if (auto propUnionTy = get<UnionType>(propTy))
|
||||||
{
|
{
|
||||||
for (TypeId option : propUnionTy->options)
|
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
|
else // property is a singular type or intersection type -> we can simply append
|
||||||
result.insert(propTy);
|
result.insert(propTy);
|
||||||
|
@ -2641,7 +2450,12 @@ bool searchPropsAndIndexer(
|
||||||
if (auto idxResUnionTy = get<UnionType>(idxResultTy))
|
if (auto idxResUnionTy = get<UnionType>(idxResultTy))
|
||||||
{
|
{
|
||||||
for (TypeId option : idxResUnionTy->options)
|
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
|
else // indexResultType is a singular type or intersection type -> we can simply append
|
||||||
result.insert(idxResultTy);
|
result.insert(idxResultTy);
|
||||||
|
@ -2656,7 +2470,7 @@ bool searchPropsAndIndexer(
|
||||||
/* Handles recursion / metamethods of tables/classes
|
/* Handles recursion / metamethods of tables/classes
|
||||||
`isRaw` parameter indicates whether or not we should follow __index metamethods
|
`isRaw` parameter indicates whether or not we should follow __index metamethods
|
||||||
returns false if property of `ty` could not be found */
|
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);
|
indexer = follow(indexer);
|
||||||
indexee = follow(indexee);
|
indexee = follow(indexee);
|
||||||
|
@ -2686,13 +2500,113 @@ bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result,
|
||||||
ErrorVec dummy;
|
ErrorVec dummy;
|
||||||
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, indexee, "__index", Location{});
|
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, indexee, "__index", Location{});
|
||||||
if (mmType)
|
if (mmType)
|
||||||
return tblIndexInto(indexer, *mmType, result, ctx, isRaw);
|
return tblIndexInto_DEPRECATED(indexer, *mmType, result, ctx, isRaw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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,
|
/* Vocabulary note: indexee refers to the type that contains the properties,
|
||||||
indexer refers to the type that is used to access indexee
|
indexer refers to the type that is used to access indexee
|
||||||
Example: index<Person, "name"> => `Person` is the indexee and `"name"` is the indexer */
|
Example: index<Person, "name"> => `Person` is the indexee and `"name"` is the indexer */
|
||||||
|
@ -2710,6 +2624,13 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
|
||||||
if (!indexeeNormTy)
|
if (!indexeeNormTy)
|
||||||
return {std::nullopt, Reduction::MaybeOk, {}, {}};
|
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 we don't have either just tables or just classes, we've got nothing to index into
|
||||||
if (indexeeNormTy->hasTables() == indexeeNormTy->hasClasses())
|
if (indexeeNormTy->hasTables() == indexeeNormTy->hasClasses())
|
||||||
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||||
|
@ -2809,17 +2730,19 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call `follow()` on each element to resolve all Bound types before returning
|
if (!FFlag::LuauIndexTypeFunctionImprovements)
|
||||||
std::transform(
|
{
|
||||||
properties.begin(),
|
// Call `follow()` on each element to resolve all Bound types before returning
|
||||||
properties.end(),
|
std::transform(
|
||||||
properties.begin(),
|
properties.begin(),
|
||||||
[](TypeId ty)
|
properties.end(),
|
||||||
{
|
properties.begin(),
|
||||||
return follow(ty);
|
[](TypeId ty)
|
||||||
}
|
{
|
||||||
|
return follow(ty);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// If the type being reduced to is a single type, no need to union
|
// If the type being reduced to is a single type, no need to union
|
||||||
if (properties.size() == 1)
|
if (properties.size() == 1)
|
||||||
return {*properties.begin(), Reduction::MaybeOk, {}, {}};
|
return {*properties.begin(), Reduction::MaybeOk, {}, {}};
|
||||||
|
|
|
@ -13,10 +13,7 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunFixHydratedClasses)
|
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunSingletonEquality)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypeofReturnsType)
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
|
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
|
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
|
||||||
|
|
||||||
|
@ -1617,11 +1614,8 @@ void registerTypeUserData(lua_State* L)
|
||||||
// Create and register metatable for type userdata
|
// Create and register metatable for type userdata
|
||||||
luaL_newmetatable(L, "type");
|
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
|
// Protect metatable from being changed
|
||||||
lua_pushstring(L, "The metatable is locked");
|
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* lp = get<TypeFunctionBooleanSingleton>(&lhs);
|
||||||
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
|
const TypeFunctionBooleanSingleton* rp = get<TypeFunctionBooleanSingleton>(&rhs);
|
||||||
if (lp && rp)
|
if (lp && rp)
|
||||||
return lp->value == rp->value;
|
return lp->value == rp->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs);
|
const TypeFunctionStringSingleton* lp = get<TypeFunctionStringSingleton>(&lhs);
|
||||||
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(FFlag::LuauTypeFunSingletonEquality ? &rhs : &lhs);
|
const TypeFunctionStringSingleton* rp = get<TypeFunctionStringSingleton>(&rhs);
|
||||||
if (lp && rp)
|
if (lp && rp)
|
||||||
return lp->value == rp->value;
|
return lp->value == rp->value;
|
||||||
}
|
}
|
||||||
|
@ -1918,10 +1912,7 @@ bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctio
|
||||||
if (seenSetContains(seen, &lhs, &rhs))
|
if (seenSetContains(seen, &lhs, &rhs))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (FFlag::LuauTypeFunFixHydratedClasses)
|
return lhs.classTy == rhs.classTy;
|
||||||
return lhs.classTy == rhs.classTy;
|
|
||||||
else
|
|
||||||
return lhs.name_DEPRECATED == rhs.name_DEPRECATED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs)
|
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
|
// used to control the recursion limit of any operations done by user-defined type functions
|
||||||
// currently, controls serialization, deserialization, and `type.copy`
|
// currently, controls serialization, deserialization, and `type.copy`
|
||||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
|
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
|
||||||
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
|
|
||||||
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
|
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -209,19 +208,11 @@ private:
|
||||||
}
|
}
|
||||||
else if (auto c = get<ClassType>(ty))
|
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
|
||||||
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the
|
target = typeFunctionRuntime->typeArena.allocate(
|
||||||
// original class
|
TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
|
||||||
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}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (auto g = get<GenericType>(ty))
|
else if (auto g = get<GenericType>(ty))
|
||||||
{
|
{
|
||||||
|
@ -713,17 +704,7 @@ private:
|
||||||
}
|
}
|
||||||
else if (auto c = get<TypeFunctionClassType>(ty))
|
else if (auto c = get<TypeFunctionClassType>(ty))
|
||||||
{
|
{
|
||||||
if (FFlag::LuauTypeFunFixHydratedClasses)
|
target = c->classTy;
|
||||||
{
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (auto g = get<TypeFunctionGenericType>(ty))
|
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;
|
TypeId ty = checkExpr(scope, *typeOf->expr).type;
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
|
else if (annotation.is<AstTypeOptional>())
|
||||||
|
{
|
||||||
|
return builtinTypes->nilType;
|
||||||
|
}
|
||||||
else if (const auto& un = annotation.as<AstTypeUnion>())
|
else if (const auto& un = annotation.as<AstTypeUnion>())
|
||||||
{
|
{
|
||||||
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode);
|
LUAU_FASTFLAGVARIABLE(LuauDisableNewSolverAssertsInMixedMode)
|
||||||
|
|
||||||
// Maximum number of steps to follow when traversing a path. May not always
|
// 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
|
// equate to the number of components in a path, depending on the traversal
|
||||||
// logic.
|
// logic.
|
||||||
|
@ -638,6 +639,247 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
|
||||||
return result.str();
|
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)
|
static bool traverse(TraversalState& state, const Path& path)
|
||||||
{
|
{
|
||||||
auto step = [&state](auto&& c)
|
auto step = [&state](auto&& c)
|
||||||
|
|
|
@ -677,7 +677,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
DenseHashSet<const void*> seenPositive{nullptr};
|
DenseHashSet<const void*> seenPositive{nullptr};
|
||||||
DenseHashSet<const void*> seenNegative{nullptr};
|
DenseHashSet<const void*> seenNegative{nullptr};
|
||||||
|
|
||||||
bool seenWithPolarity(const void* ty)
|
bool seenWithCurrentPolarity(const void* ty)
|
||||||
{
|
{
|
||||||
switch (polarity)
|
switch (polarity)
|
||||||
{
|
{
|
||||||
|
@ -719,7 +719,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypeId ty) override
|
bool visit(TypeId ty) override
|
||||||
{
|
{
|
||||||
if (seenWithPolarity(ty))
|
if (seenWithCurrentPolarity(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
LUAU_ASSERT(ty);
|
LUAU_ASSERT(ty);
|
||||||
|
@ -728,7 +728,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypeId ty, const FreeType& ft) override
|
bool visit(TypeId ty, const FreeType& ft) override
|
||||||
{
|
{
|
||||||
if (seenWithPolarity(ty))
|
if (seenWithCurrentPolarity(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!subsumes(scope, ft.scope))
|
if (!subsumes(scope, ft.scope))
|
||||||
|
@ -753,7 +753,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypeId ty, const TableType& tt) override
|
bool visit(TypeId ty, const TableType& tt) override
|
||||||
{
|
{
|
||||||
if (seenWithPolarity(ty))
|
if (seenWithCurrentPolarity(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
|
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
|
bool visit(TypeId ty, const FunctionType& ft) override
|
||||||
{
|
{
|
||||||
if (seenWithPolarity(ty))
|
if (seenWithCurrentPolarity(ty))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
flip();
|
flip();
|
||||||
|
@ -818,7 +818,7 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
|
|
||||||
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
||||||
{
|
{
|
||||||
if (seenWithPolarity(tp))
|
if (seenWithCurrentPolarity(tp))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!subsumes(scope, ftp.scope))
|
if (!subsumes(scope, ftp.scope))
|
||||||
|
|
|
@ -1127,6 +1127,16 @@ public:
|
||||||
AstExpr* expr;
|
AstExpr* expr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AstTypeOptional : public AstType
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_RTTI(AstTypeOptional)
|
||||||
|
|
||||||
|
AstTypeOptional(const Location& location);
|
||||||
|
|
||||||
|
void visit(AstVisitor* visitor) override;
|
||||||
|
};
|
||||||
|
|
||||||
class AstTypeUnion : public AstType
|
class AstTypeUnion : public AstType
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -1488,6 +1498,10 @@ public:
|
||||||
{
|
{
|
||||||
return visit(static_cast<AstType*>(node));
|
return visit(static_cast<AstType*>(node));
|
||||||
}
|
}
|
||||||
|
virtual bool visit(class AstTypeOptional* node)
|
||||||
|
{
|
||||||
|
return visit(static_cast<AstType*>(node));
|
||||||
|
}
|
||||||
virtual bool visit(class AstTypeUnion* node)
|
virtual bool visit(class AstTypeUnion* node)
|
||||||
{
|
{
|
||||||
return visit(static_cast<AstType*>(node));
|
return visit(static_cast<AstType*>(node));
|
||||||
|
|
|
@ -105,6 +105,20 @@ public:
|
||||||
Position closeBracketPosition;
|
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
|
class CstExprTable : public CstNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -311,6 +325,17 @@ public:
|
||||||
Position equalsPosition;
|
Position equalsPosition;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CstStatTypeFunction : public CstNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUAU_CST_RTTI(CstStatTypeFunction)
|
||||||
|
|
||||||
|
CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition);
|
||||||
|
|
||||||
|
Position typeKeywordPosition;
|
||||||
|
Position functionKeywordPosition;
|
||||||
|
};
|
||||||
|
|
||||||
class CstTypeReference : public CstNode
|
class CstTypeReference : public CstNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -359,6 +384,32 @@ public:
|
||||||
bool isArray = false;
|
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
|
class CstTypeTypeof : public CstNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -382,4 +433,26 @@ public:
|
||||||
unsigned int blockDepth;
|
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
|
} // namespace Luau
|
|
@ -155,7 +155,7 @@ private:
|
||||||
AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition);
|
AstStat* parseTypeAlias(const Location& start, bool exported, Position typeKeywordPosition);
|
||||||
|
|
||||||
// type function Name ... end
|
// type function Name ... end
|
||||||
AstStat* parseTypeFunction(const Location& start, bool exported);
|
AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition);
|
||||||
|
|
||||||
AstDeclaredClassProp parseDeclaredClassMethod();
|
AstDeclaredClassProp parseDeclaredClassMethod();
|
||||||
|
|
||||||
|
@ -192,7 +192,8 @@ private:
|
||||||
std::tuple<bool, Location, AstTypePack*> parseBindingList(
|
std::tuple<bool, Location, AstTypePack*> parseBindingList(
|
||||||
TempVector<Binding>& result,
|
TempVector<Binding>& result,
|
||||||
bool allowDot3 = false,
|
bool allowDot3 = false,
|
||||||
TempVector<Position>* commaPositions = nullptr
|
AstArray<Position>* commaPositions = nullptr,
|
||||||
|
std::optional<Position> initialCommaPosition = std::nullopt
|
||||||
);
|
);
|
||||||
|
|
||||||
AstType* parseOptionalType();
|
AstType* parseOptionalType();
|
||||||
|
@ -209,9 +210,14 @@ private:
|
||||||
// | `(' [TypeList] `)' `->` ReturnType
|
// | `(' [TypeList] `)' `->` ReturnType
|
||||||
|
|
||||||
// Returns the variadic annotation, if it exists.
|
// 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();
|
std::pair<Location, AstTypeList> parseReturnType();
|
||||||
|
|
||||||
struct TableIndexerResult
|
struct TableIndexerResult
|
||||||
|
@ -305,7 +311,7 @@ private:
|
||||||
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList(
|
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> parseGenericTypeList(
|
||||||
bool withDefaultValues,
|
bool withDefaultValues,
|
||||||
Position* openPosition = nullptr,
|
Position* openPosition = nullptr,
|
||||||
TempVector<Position>* commaPositions = nullptr,
|
AstArray<Position>* commaPositions = nullptr,
|
||||||
Position* closePosition = nullptr
|
Position* closePosition = nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -491,6 +497,7 @@ private:
|
||||||
std::vector<AstGenericTypePack*> scratchGenericTypePacks;
|
std::vector<AstGenericTypePack*> scratchGenericTypePacks;
|
||||||
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
std::vector<std::optional<AstArgumentName>> scratchOptArgName;
|
||||||
std::vector<Position> scratchPosition;
|
std::vector<Position> scratchPosition;
|
||||||
|
std::vector<std::optional<Position>> scratchOptPosition;
|
||||||
std::string scratchData;
|
std::string scratchData;
|
||||||
|
|
||||||
CstNodeMap cstNodeMap;
|
CstNodeMap cstNodeMap;
|
||||||
|
|
|
@ -1069,6 +1069,16 @@ void AstTypeTypeof::visit(AstVisitor* visitor)
|
||||||
expr->visit(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)
|
AstTypeUnion::AstTypeUnion(const Location& location, const AstArray<AstType*>& types)
|
||||||
: AstType(ClassIndex(), location)
|
: AstType(ClassIndex(), location)
|
||||||
, types(types)
|
, types(types)
|
||||||
|
|
|
@ -38,6 +38,10 @@ CstExprIndexExpr::CstExprIndexExpr(Position openBracketPosition, Position closeB
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CstExprFunction::CstExprFunction() : CstNode(CstClassIndex())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
CstExprTable::CstExprTable(const AstArray<Item>& items)
|
CstExprTable::CstExprTable(const AstArray<Item>& items)
|
||||||
: CstNode(CstClassIndex())
|
: CstNode(CstClassIndex())
|
||||||
, items(items)
|
, items(items)
|
||||||
|
@ -160,6 +164,13 @@ CstStatTypeAlias::CstStatTypeAlias(
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CstStatTypeFunction::CstStatTypeFunction(Position typeKeywordPosition, Position functionKeywordPosition)
|
||||||
|
: CstNode(CstClassIndex())
|
||||||
|
, typeKeywordPosition(typeKeywordPosition)
|
||||||
|
, functionKeywordPosition(functionKeywordPosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
CstTypeReference::CstTypeReference(
|
CstTypeReference::CstTypeReference(
|
||||||
std::optional<Position> prefixPointPosition,
|
std::optional<Position> prefixPointPosition,
|
||||||
Position openParametersPosition,
|
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)
|
CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
|
||||||
: CstNode(CstClassIndex())
|
: CstNode(CstClassIndex())
|
||||||
, openPosition(openPosition)
|
, openPosition(openPosition)
|
||||||
|
@ -197,4 +230,18 @@ CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstE
|
||||||
LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp);
|
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
|
} // namespace Luau
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LexerFixInterpStringStart)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -789,7 +787,7 @@ Lexeme Lexer::readNext()
|
||||||
return Lexeme(Location(start, 1), '}');
|
return Lexeme(Location(start, 1), '}');
|
||||||
}
|
}
|
||||||
|
|
||||||
return readInterpolatedStringSection(FFlag::LexerFixInterpStringStart ? start : position(), Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
|
return readInterpolatedStringSection(start, Lexeme::InterpStringMid, Lexeme::InterpStringEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
case '=':
|
case '=':
|
||||||
|
|
|
@ -27,6 +27,7 @@ LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
|
LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3)
|
||||||
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
|
LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
|
LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -647,16 +648,16 @@ AstStat* Parser::parseFor()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TempVector<Binding> names(scratchBinding);
|
TempVector<Binding> names(scratchBinding);
|
||||||
TempVector<Position> varsCommaPosition(scratchPosition);
|
AstArray<Position> varsCommaPosition;
|
||||||
names.push_back(varname);
|
names.push_back(varname);
|
||||||
|
|
||||||
if (lexer.current().type == ',')
|
if (lexer.current().type == ',')
|
||||||
{
|
{
|
||||||
if (FFlag::LuauStoreCSTData && options.storeCstData)
|
if (FFlag::LuauStoreCSTData && options.storeCstData)
|
||||||
{
|
{
|
||||||
varsCommaPosition.push_back(lexer.current().location.begin);
|
Position initialCommaPosition = lexer.current().location.begin;
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
parseBindingList(names, false, &varsCommaPosition);
|
parseBindingList(names, false, &varsCommaPosition, initialCommaPosition);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -701,7 +702,7 @@ AstStat* Parser::parseFor()
|
||||||
AstStatForIn* node =
|
AstStatForIn* node =
|
||||||
allocator.alloc<AstStatForIn>(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location);
|
allocator.alloc<AstStatForIn>(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location);
|
||||||
if (options.storeCstData)
|
if (options.storeCstData)
|
||||||
cstNodeMap[node] = allocator.alloc<CstStatForIn>(copy(varsCommaPosition), copy(valuesCommaPositions));
|
cstNodeMap[node] = allocator.alloc<CstStatForIn>(varsCommaPosition, copy(valuesCommaPositions));
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -957,7 +958,7 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
|
||||||
matchRecoveryStopOnToken['=']++;
|
matchRecoveryStopOnToken['=']++;
|
||||||
|
|
||||||
TempVector<Binding> names(scratchBinding);
|
TempVector<Binding> names(scratchBinding);
|
||||||
TempVector<Position> varsCommaPositions(scratchPosition);
|
AstArray<Position> varsCommaPositions;
|
||||||
if (FFlag::LuauStoreCSTData && options.storeCstData)
|
if (FFlag::LuauStoreCSTData && options.storeCstData)
|
||||||
parseBindingList(names, false, &varsCommaPositions);
|
parseBindingList(names, false, &varsCommaPositions);
|
||||||
else
|
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);
|
AstStatLocal* node = allocator.alloc<AstStatLocal>(Location(start, end), copy(vars), copy(values), equalsSignLocation);
|
||||||
if (options.storeCstData)
|
if (options.storeCstData)
|
||||||
cstNodeMap[node] = allocator.alloc<CstStatLocal>(copy(varsCommaPositions), copy(valuesCommaPositions));
|
cstNodeMap[node] = allocator.alloc<CstStatLocal>(varsCommaPositions, copy(valuesCommaPositions));
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1033,7 +1034,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position t
|
||||||
{
|
{
|
||||||
// parsing a type function
|
// parsing a type function
|
||||||
if (lexer.current().type == Lexeme::ReservedFunction)
|
if (lexer.current().type == Lexeme::ReservedFunction)
|
||||||
return parseTypeFunction(start, exported);
|
return parseTypeFunction(start, exported, typeKeywordPosition);
|
||||||
|
|
||||||
// parsing a type alias
|
// parsing a type alias
|
||||||
|
|
||||||
|
@ -1046,7 +1047,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position t
|
||||||
name = Name(nameError, lexer.current().location);
|
name = Name(nameError, lexer.current().location);
|
||||||
|
|
||||||
Position genericsOpenPosition{0, 0};
|
Position genericsOpenPosition{0, 0};
|
||||||
TempVector<Position> genericsCommaPositions(scratchPosition);
|
AstArray<Position> genericsCommaPositions;
|
||||||
Position genericsClosePosition{0, 0};
|
Position genericsClosePosition{0, 0};
|
||||||
auto [generics, genericPacks] = FFlag::LuauStoreCSTData && options.storeCstData
|
auto [generics, genericPacks] = FFlag::LuauStoreCSTData && options.storeCstData
|
||||||
? parseGenericTypeList(
|
? 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);
|
allocator.alloc<AstStatTypeAlias>(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported);
|
||||||
if (options.storeCstData)
|
if (options.storeCstData)
|
||||||
cstNodeMap[node] = allocator.alloc<CstStatTypeAlias>(
|
cstNodeMap[node] = allocator.alloc<CstStatTypeAlias>(
|
||||||
typeKeywordPosition, genericsOpenPosition, copy(genericsCommaPositions), genericsClosePosition, equalsPosition
|
typeKeywordPosition, genericsOpenPosition, genericsCommaPositions, genericsClosePosition, equalsPosition
|
||||||
);
|
);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -1076,7 +1077,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position t
|
||||||
}
|
}
|
||||||
|
|
||||||
// type function Name `(' arglist `)' `=' funcbody `end'
|
// 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();
|
Lexeme matchFn = lexer.current();
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
|
@ -1097,7 +1098,18 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported)
|
||||||
|
|
||||||
matchRecoveryStopOnToken[Lexeme::ReservedEnd]--;
|
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()
|
AstDeclaredClassProp Parser::parseDeclaredClassMethod()
|
||||||
|
@ -1439,7 +1451,15 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||||
{
|
{
|
||||||
Location start = matchFunction.location;
|
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();
|
MatchLexeme matchParen = lexer.current();
|
||||||
expectAndConsume('(', "function");
|
expectAndConsume('(', "function");
|
||||||
|
@ -1464,7 +1484,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||||
AstTypePack* varargAnnotation = nullptr;
|
AstTypePack* varargAnnotation = nullptr;
|
||||||
|
|
||||||
if (lexer.current().type != ')')
|
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;
|
std::optional<Location> argLocation;
|
||||||
|
|
||||||
|
@ -1476,7 +1497,7 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||||
if (FFlag::LuauErrorRecoveryForTableTypes)
|
if (FFlag::LuauErrorRecoveryForTableTypes)
|
||||||
matchRecoveryStopOnToken[')']--;
|
matchRecoveryStopOnToken[')']--;
|
||||||
|
|
||||||
std::optional<AstTypeList> typelist = parseOptionalReturnType();
|
std::optional<AstTypeList> typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr);
|
||||||
|
|
||||||
AstLocal* funLocal = nullptr;
|
AstLocal* funLocal = nullptr;
|
||||||
|
|
||||||
|
@ -1503,8 +1524,9 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||||
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
|
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
|
||||||
body->hasEnd = hasEnd;
|
body->hasEnd = hasEnd;
|
||||||
|
|
||||||
return {
|
if (FFlag::LuauStoreCSTData)
|
||||||
allocator.alloc<AstExprFunction>(
|
{
|
||||||
|
AstExprFunction* node = allocator.alloc<AstExprFunction>(
|
||||||
Location(start, end),
|
Location(start, end),
|
||||||
attributes,
|
attributes,
|
||||||
generics,
|
generics,
|
||||||
|
@ -1519,9 +1541,34 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||||
typelist,
|
typelist,
|
||||||
varargAnnotation,
|
varargAnnotation,
|
||||||
argLocation
|
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
|
// explist ::= {exp `,'} exp
|
||||||
|
@ -1559,8 +1606,13 @@ Parser::Binding Parser::parseBinding()
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindinglist ::= (binding | `...') [`,' bindinglist]
|
// 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)
|
while (true)
|
||||||
{
|
{
|
||||||
if (lexer.current().type == Lexeme::Dot3 && allowDot3)
|
if (lexer.current().type == Lexeme::Dot3 && allowDot3)
|
||||||
|
@ -1575,6 +1627,9 @@ std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(TempVector<Bin
|
||||||
tailAnnotation = parseVariadicArgumentTypePack();
|
tailAnnotation = parseVariadicArgumentTypePack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||||
|
*commaPositions = copy(localCommaPositions);
|
||||||
|
|
||||||
return {true, varargLocation, tailAnnotation};
|
return {true, varargLocation, tailAnnotation};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1583,10 +1638,13 @@ std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(TempVector<Bin
|
||||||
if (lexer.current().type != ',')
|
if (lexer.current().type != ',')
|
||||||
break;
|
break;
|
||||||
if (FFlag::LuauStoreCSTData && commaPositions)
|
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||||
commaPositions->push_back(lexer.current().location.begin);
|
localCommaPositions.push_back(lexer.current().location.begin);
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||||
|
*commaPositions = copy(localCommaPositions);
|
||||||
|
|
||||||
return {false, Location(), nullptr};
|
return {false, Location(), nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1602,7 +1660,12 @@ AstType* Parser::parseOptionalType()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TypeList ::= Type [`,' TypeList] | ...Type
|
// 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)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -1614,22 +1677,33 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
|
||||||
// Fill in previous argument names with empty slots
|
// Fill in previous argument names with empty slots
|
||||||
while (resultNames.size() < result.size())
|
while (resultNames.size() < result.size())
|
||||||
resultNames.push_back({});
|
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});
|
resultNames.push_back(AstArgumentName{AstName(lexer.current().name), lexer.current().location});
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
|
|
||||||
|
if (FFlag::LuauStoreCSTData && nameColonPositions)
|
||||||
|
nameColonPositions->push_back(lexer.current().location.begin);
|
||||||
expectAndConsume(':');
|
expectAndConsume(':');
|
||||||
}
|
}
|
||||||
else if (!resultNames.empty())
|
else if (!resultNames.empty())
|
||||||
{
|
{
|
||||||
// If we have a type with named arguments, provide elements for all types
|
// If we have a type with named arguments, provide elements for all types
|
||||||
resultNames.push_back({});
|
resultNames.push_back({});
|
||||||
|
if (FFlag::LuauStoreCSTData && nameColonPositions)
|
||||||
|
nameColonPositions->push_back({});
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push_back(parseType());
|
result.push_back(parseType());
|
||||||
if (lexer.current().type != ',')
|
if (lexer.current().type != ',')
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||||
|
commaPositions->push_back(lexer.current().location.begin);
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
|
|
||||||
if (lexer.current().type == ')')
|
if (lexer.current().type == ')')
|
||||||
|
@ -1642,13 +1716,15 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
|
||||||
return nullptr;
|
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 == ':' || lexer.current().type == Lexeme::SkinnyArrow)
|
||||||
{
|
{
|
||||||
if (lexer.current().type == Lexeme::SkinnyArrow)
|
if (lexer.current().type == Lexeme::SkinnyArrow)
|
||||||
report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'");
|
report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'");
|
||||||
|
|
||||||
|
if (FFlag::LuauStoreCSTData && returnSpecifierPosition)
|
||||||
|
*returnSpecifierPosition = lexer.current().location.begin;
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
|
|
||||||
unsigned int oldRecursionCount = recursionCounter;
|
unsigned int oldRecursionCount = recursionCounter;
|
||||||
|
@ -2016,7 +2092,14 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
|
||||||
|
|
||||||
Lexeme begin = lexer.current();
|
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();
|
Lexeme parameterStart = lexer.current();
|
||||||
|
|
||||||
|
@ -2026,10 +2109,17 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
|
||||||
|
|
||||||
TempVector<AstType*> params(scratchType);
|
TempVector<AstType*> params(scratchType);
|
||||||
TempVector<std::optional<AstArgumentName>> names(scratchOptArgName);
|
TempVector<std::optional<AstArgumentName>> names(scratchOptArgName);
|
||||||
|
TempVector<std::optional<Position>> nameColonPositions(scratchOptPosition);
|
||||||
|
TempVector<Position> argCommaPositions(scratchPosition);
|
||||||
AstTypePack* varargAnnotation = nullptr;
|
AstTypePack* varargAnnotation = nullptr;
|
||||||
|
|
||||||
if (lexer.current().type != ')')
|
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;
|
Location closeArgsLocation = lexer.current().location;
|
||||||
expectMatchAndConsume(')', parameterStart, true);
|
expectMatchAndConsume(')', parameterStart, true);
|
||||||
|
@ -2047,7 +2137,20 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
|
||||||
if (params.size() == 1 && !varargAnnotation && !forceFunctionType && !returnTypeIntroducer)
|
if (params.size() == 1 && !varargAnnotation && !forceFunctionType && !returnTypeIntroducer)
|
||||||
{
|
{
|
||||||
if (allowPack)
|
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
|
else
|
||||||
{
|
{
|
||||||
if (FFlag::LuauAstTypeGroup3)
|
if (FFlag::LuauAstTypeGroup3)
|
||||||
|
@ -2058,11 +2161,46 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray<AstAttr*>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!forceFunctionType && !returnTypeIntroducer && allowPack)
|
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);
|
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(
|
AstType* Parser::parseFunctionTypeTail(
|
||||||
|
@ -2124,7 +2262,9 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
|
||||||
|
|
||||||
bool isUnion = false;
|
bool isUnion = false;
|
||||||
bool isIntersection = false;
|
bool isIntersection = false;
|
||||||
bool hasOptional = false;
|
// Clip with FFlag::LuauParseOptionalAsNode
|
||||||
|
bool hasOptional_DEPRECATED = false;
|
||||||
|
unsigned int optionalCount = 0;
|
||||||
|
|
||||||
Location location = begin;
|
Location location = begin;
|
||||||
|
|
||||||
|
@ -2148,11 +2288,19 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
|
||||||
Location loc = lexer.current().location;
|
Location loc = lexer.current().location;
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
|
|
||||||
if (!hasOptional)
|
if (FFlag::LuauParseOptionalAsNode)
|
||||||
parts.push_back(allocator.alloc<AstTypeReference>(loc, std::nullopt, nameNil, std::nullopt, loc));
|
{
|
||||||
|
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;
|
isUnion = true;
|
||||||
hasOptional = true;
|
hasOptional_DEPRECATED = true;
|
||||||
}
|
}
|
||||||
else if (c == '&')
|
else if (c == '&')
|
||||||
{
|
{
|
||||||
|
@ -2172,8 +2320,16 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (parts.size() > unsigned(FInt::LuauTypeLengthLimit) + hasOptional)
|
if (FFlag::LuauParseOptionalAsNode)
|
||||||
ParseError::raise(parts.back()->location, "Exceeded allowed type length; simplify your type annotation to make the code compile");
|
{
|
||||||
|
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)
|
if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
|
@ -2465,7 +2621,17 @@ AstTypePack* Parser::parseVariadicArgumentTypePack()
|
||||||
|
|
||||||
// This will not fail because of the lookahead guard.
|
// This will not fail because of the lookahead guard.
|
||||||
expectAndConsume(Lexeme::Dot3, "generic type pack annotation");
|
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
|
// Variadic: T
|
||||||
else
|
else
|
||||||
|
@ -2493,7 +2659,17 @@ AstTypePack* Parser::parseTypePack()
|
||||||
|
|
||||||
// This will not fail because of the lookahead guard.
|
// This will not fail because of the lookahead guard.
|
||||||
expectAndConsume(Lexeme::Dot3, "generic type pack annotation");
|
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
|
// 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(
|
std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::parseGenericTypeList(
|
||||||
bool withDefaultValues,
|
bool withDefaultValues,
|
||||||
Position* openPosition,
|
Position* openPosition,
|
||||||
TempVector<Position>* commaPositions,
|
AstArray<Position>* commaPositions,
|
||||||
Position* closePosition
|
Position* closePosition
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
TempVector<AstGenericType*> names{scratchGenericTypes};
|
TempVector<AstGenericType*> names{scratchGenericTypes};
|
||||||
TempVector<AstGenericTypePack*> namePacks{scratchGenericTypePacks};
|
TempVector<AstGenericTypePack*> namePacks{scratchGenericTypePacks};
|
||||||
|
TempVector<Position> localCommaPositions{scratchPosition};
|
||||||
|
|
||||||
if (lexer.current().type == '<')
|
if (lexer.current().type == '<')
|
||||||
{
|
{
|
||||||
|
@ -3483,7 +3660,7 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
|
||||||
if (lexer.current().type == ',')
|
if (lexer.current().type == ',')
|
||||||
{
|
{
|
||||||
if (FFlag::LuauStoreCSTData && commaPositions)
|
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||||
commaPositions->push_back(lexer.current().location.begin);
|
localCommaPositions.push_back(lexer.current().location.begin);
|
||||||
nextLexeme();
|
nextLexeme();
|
||||||
|
|
||||||
if (lexer.current().type == '>')
|
if (lexer.current().type == '>')
|
||||||
|
@ -3501,6 +3678,9 @@ std::pair<AstArray<AstGenericType*>, AstArray<AstGenericTypePack*>> Parser::pars
|
||||||
expectMatchAndConsume('>', begin);
|
expectMatchAndConsume('>', begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauStoreCSTData && commaPositions)
|
||||||
|
*commaPositions = copy(localCommaPositions);
|
||||||
|
|
||||||
AstArray<AstGenericType*> generics = copy(names);
|
AstArray<AstGenericType*> generics = copy(names);
|
||||||
AstArray<AstGenericTypePack*> genericPacks = copy(namePacks);
|
AstArray<AstGenericTypePack*> genericPacks = copy(namePacks);
|
||||||
return {generics, genericPacks};
|
return {generics, genericPacks};
|
||||||
|
|
|
@ -125,6 +125,10 @@ static LuauBytecodeType getType(
|
||||||
{
|
{
|
||||||
return getType(group->type, generics, typeAliases, resolveAliases, hostVectorType, userdataTypes, bytecode);
|
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;
|
return LBC_TYPE_ANY;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,9 +53,9 @@ type A = (number, string) -> ...any
|
||||||
|
|
||||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)");
|
CHECK(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "export_alias")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||||
if (FFlag::LuauStoreCSTData && FFlag::LuauAstTypeGroup3)
|
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)
|
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)
|
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
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk);
|
CHECK(module->ats.typeInfo[1].code == Pattern::TypePk);
|
||||||
LUAU_ASSERT(
|
CHECK(
|
||||||
module->ats.typeInfo[0].node ==
|
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"
|
"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");
|
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")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = {first: T, second: any}");
|
CHECK(module->ats.typeInfo[0].node == "type Pair<T> = {first: T, second: any}");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "assign_uneq")
|
TEST_CASE_FIXTURE(ATSFixture, "assign_uneq")
|
||||||
|
@ -190,7 +190,7 @@ local x, y, z = greetings("Dibri") -- mismatch
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
||||||
|
|
||||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/B");
|
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")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = (boolean, T)->( ...any)");
|
CHECK(module->ats.typeInfo[0].node == "type Pair<T> = (boolean, T)->( ...any)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "typeof_any_in_func")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
CHECK(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");
|
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")
|
TEST_CASE_FIXTURE(ATSFixture, "generic_types")
|
||||||
|
@ -264,9 +264,9 @@ foo(addNumbers)
|
||||||
|
|
||||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncApp);
|
CHECK(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");
|
CHECK(module->ats.typeInfo[0].node == "local function foo<A>(a: (...A)->( any),...: A)\n return a(...)\nend");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "no_annot")
|
TEST_CASE_FIXTURE(ATSFixture, "no_annot")
|
||||||
|
@ -285,7 +285,7 @@ local character = script.Parent
|
||||||
|
|
||||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
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")
|
TEST_CASE_FIXTURE(ATSFixture, "if_any")
|
||||||
|
@ -314,9 +314,9 @@ end
|
||||||
|
|
||||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||||
LUAU_ASSERT(
|
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 = "
|
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"
|
"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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
|
CHECK(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");
|
CHECK(module->ats.typeInfo[0].node == "local function f(): (number, ...any)\n return 1, 5\n end");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[2].code == Pattern::VarAnnot);
|
CHECK(module->ats.typeInfo[2].code == Pattern::VarAnnot);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}");
|
CHECK(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "var_func_arg")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 4);
|
REQUIRE(module->ats.typeInfo.size() == 4);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
|
CHECK(module->ats.typeInfo[0].code == Pattern::VarAny);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
|
CHECK(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "var_func_apps")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
|
CHECK(module->ats.typeInfo[0].code == Pattern::VarAny);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
|
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");
|
CHECK_EQ(module->ats.typeInfo[0].node, "descendant.CollisionGroup = CAR_COLLISION_GROUP");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
REQUIRE(module->ats.typeInfo.size() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol")
|
TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol")
|
||||||
|
@ -477,9 +477,9 @@ end
|
||||||
|
|
||||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
CHECK(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "racing_3_short")
|
TEST_CASE_FIXTURE(ATSFixture, "racing_3_short")
|
||||||
|
@ -518,9 +518,9 @@ initialize()
|
||||||
|
|
||||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 5);
|
REQUIRE(module->ats.typeInfo.size() == 5);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
CHECK(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
|
TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
|
||||||
|
@ -596,10 +596,10 @@ initialize()
|
||||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
if (FFlag::LuauSkipNoRefineDuringRefinement)
|
if (FFlag::LuauSkipNoRefineDuringRefinement)
|
||||||
CHECK_EQ(module->ats.typeInfo.size(), 12);
|
REQUIRE_EQ(module->ats.typeInfo.size(), 12);
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
|
REQUIRE(module->ats.typeInfo.size() == 11);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||||
if (FFlag::LuauStoreCSTData)
|
if (FFlag::LuauStoreCSTData)
|
||||||
{
|
{
|
||||||
CHECK_EQ(
|
CHECK_EQ(
|
||||||
|
@ -612,7 +612,7 @@ initialize()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(
|
CHECK(
|
||||||
module->ats.typeInfo[0].node ==
|
module->ats.typeInfo[0].node ==
|
||||||
"local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if "
|
"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 "
|
"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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 7);
|
REQUIRE(module->ats.typeInfo.size() == 7);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||||
LUAU_ASSERT(
|
CHECK(
|
||||||
module->ats.typeInfo[0].node ==
|
module->ats.typeInfo[0].node ==
|
||||||
"local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, "
|
"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, "
|
"`{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");
|
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")
|
TEST_CASE_FIXTURE(ATSFixture, "explicit_pack")
|
||||||
|
@ -739,9 +739,9 @@ type Bar = Foo<(number, any)>
|
||||||
|
|
||||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>");
|
CHECK(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "local_val")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Casts);
|
CHECK(module->ats.typeInfo[0].code == Pattern::Casts);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local a, b, c = 1 :: any");
|
CHECK(module->ats.typeInfo[0].node == "local a, b, c = 1 :: any");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "var_any_local")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
CHECK(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 2, 3");
|
CHECK(module->ats.typeInfo[0].node == "local x: any = 2, 3");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "table_uses_any")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
CHECK(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 0");
|
CHECK(module->ats.typeInfo[0].node == "local x: any = 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "typeof_any")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncArg);
|
CHECK(module->ats.typeInfo[1].code == Pattern::FuncArg);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end");
|
CHECK(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Assign);
|
CHECK(module->ats.typeInfo[1].code == Pattern::Assign);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}");
|
CHECK(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
CHECK(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some(x: any)\n end");
|
CHECK(module->ats.typeInfo[0].node == "function some(x: any)\n end");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
|
CHECK(module->ats.typeInfo[0].code == Pattern::FuncRet);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end");
|
CHECK(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "nested_local")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
CHECK(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");
|
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")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
REQUIRE(module->ats.typeInfo.size() == 1);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
CHECK(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");
|
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")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
|
CHECK(module->ats.typeInfo[0].node == "type Clear = any");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "multi_module_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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/B");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
REQUIRE(module->ats.typeInfo.size() == 2);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
CHECK(module->ats.typeInfo[0].code == Pattern::Alias);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
|
CHECK(module->ats.typeInfo[0].node == "type Clear = any");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ATSFixture, "cast_on_cyclic_req")
|
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");
|
ModulePtr module = frontend.moduleResolver.getModule("game/B");
|
||||||
|
|
||||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
REQUIRE(module->ats.typeInfo.size() == 3);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Alias);
|
CHECK(module->ats.typeInfo[1].code == Pattern::Alias);
|
||||||
LUAU_ASSERT(module->ats.typeInfo[1].node == "type Clear = any");
|
CHECK(module->ats.typeInfo[1].node == "type Clear = any");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "ClassFixture.h"
|
||||||
#include "Fixture.h"
|
#include "Fixture.h"
|
||||||
#include "ScopedFlags.h"
|
#include "ScopedFlags.h"
|
||||||
|
|
||||||
|
@ -19,6 +21,8 @@ LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
|
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
|
||||||
|
LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen)
|
||||||
|
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
@ -154,6 +158,10 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ACClassFixture : ACFixtureImpl<ClassFixture>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("AutocompleteTest");
|
TEST_SUITE_BEGIN("AutocompleteTest");
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ACFixture, "empty_program")
|
TEST_CASE_FIXTURE(ACFixture, "empty_program")
|
||||||
|
@ -4416,4 +4424,76 @@ local x = 1 + result.
|
||||||
CHECK(ac.entryMap.count("x"));
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -9,7 +9,8 @@ using std::nullopt;
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
ClassFixture::ClassFixture()
|
ClassFixture::ClassFixture(bool prepareAutocomplete)
|
||||||
|
: BuiltinsFixture(prepareAutocomplete)
|
||||||
{
|
{
|
||||||
GlobalTypes& globals = frontend.globals;
|
GlobalTypes& globals = frontend.globals;
|
||||||
TypeArena& arena = globals.globalTypes;
|
TypeArena& arena = globals.globalTypes;
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace Luau
|
||||||
|
|
||||||
struct ClassFixture : BuiltinsFixture
|
struct ClassFixture : BuiltinsFixture
|
||||||
{
|
{
|
||||||
ClassFixture();
|
explicit ClassFixture(bool prepareAutocomplete = false);
|
||||||
|
|
||||||
TypeId vector2Type;
|
TypeId vector2Type;
|
||||||
TypeId vector2InstanceType;
|
TypeId vector2InstanceType;
|
||||||
|
|
|
@ -34,6 +34,7 @@ void ConstraintGeneratorFixture::generateConstraints(const std::string& code)
|
||||||
builtinTypes,
|
builtinTypes,
|
||||||
NotNull(&ice),
|
NotNull(&ice),
|
||||||
frontend.globals.globalScope,
|
frontend.globals.globalScope,
|
||||||
|
frontend.globals.globalTypeFunctionScope,
|
||||||
/*prepareModuleScope*/ nullptr,
|
/*prepareModuleScope*/ nullptr,
|
||||||
&logger,
|
&logger,
|
||||||
NotNull{dfg.get()},
|
NotNull{dfg.get()},
|
||||||
|
|
|
@ -323,6 +323,7 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
|
||||||
NotNull{&moduleResolver},
|
NotNull{&moduleResolver},
|
||||||
NotNull{&fileResolver},
|
NotNull{&fileResolver},
|
||||||
frontend.globals.globalScope,
|
frontend.globals.globalScope,
|
||||||
|
frontend.globals.globalTypeFunctionScope,
|
||||||
/*prepareModuleScope*/ nullptr,
|
/*prepareModuleScope*/ nullptr,
|
||||||
frontend.options,
|
frontend.options,
|
||||||
{},
|
{},
|
||||||
|
|
|
@ -43,6 +43,8 @@ LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
|
||||||
LUAU_FASTFLAG(LuauCloneTypeAliasBindings)
|
LUAU_FASTFLAG(LuauCloneTypeAliasBindings)
|
||||||
LUAU_FASTFLAG(LuauDoNotClonePersistentBindings)
|
LUAU_FASTFLAG(LuauDoNotClonePersistentBindings)
|
||||||
LUAU_FASTFLAG(LuauCloneReturnTypePack)
|
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)
|
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 luauCloneTypeAliasBindings{FFlag::LuauCloneTypeAliasBindings, true};
|
||||||
ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true};
|
ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true};
|
||||||
ScopedFastFlag luauCloneReturnTypePack{FFlag::LuauCloneReturnTypePack, true};
|
ScopedFastFlag luauCloneReturnTypePack{FFlag::LuauCloneReturnTypePack, true};
|
||||||
|
ScopedFastFlag luauIncrementalAutocompleteDemandBasedCloning{FFlag::LuauIncrementalAutocompleteDemandBasedCloning, true};
|
||||||
|
|
||||||
FragmentAutocompleteFixtureImpl()
|
FragmentAutocompleteFixtureImpl()
|
||||||
: BaseType(true)
|
: BaseType(true)
|
||||||
|
@ -157,12 +160,14 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
||||||
this->check(document, getOptions());
|
this->check(document, getOptions());
|
||||||
|
|
||||||
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||||
|
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
|
||||||
assertions(result);
|
assertions(result);
|
||||||
|
|
||||||
ScopedFastFlag _{FFlag::LuauSolverV2, false};
|
ScopedFastFlag _{FFlag::LuauSolverV2, false};
|
||||||
this->check(document, getOptions());
|
this->check(document, getOptions());
|
||||||
|
|
||||||
result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||||
|
CHECK(result.status != FragmentAutocompleteStatus::InternalIce);
|
||||||
assertions(result);
|
assertions(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2341,4 +2346,40 @@ z = a.P.E
|
||||||
autocompleteFragmentInBothSolvers(source, dest, Position{8, 9}, [](FragmentAutocompleteStatusResult& _) {});
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -18,6 +18,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||||
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
|
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
|
||||||
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
LUAU_FASTFLAG(LuauModuleHoldsAstRoot)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
namespace
|
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.
|
// 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 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>
|
// 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(
|
REQUIRE_EQ(
|
||||||
R"(Type
|
R"(Type
|
||||||
'{ count: string }'
|
'{ count: string }'
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LexerFixInterpStringStart)
|
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("LexerTests");
|
TEST_SUITE_BEGIN("LexerTests");
|
||||||
|
|
||||||
TEST_CASE("broken_string_works")
|
TEST_CASE("broken_string_works")
|
||||||
|
@ -156,7 +154,7 @@ TEST_CASE("string_interpolation_basic")
|
||||||
Lexeme interpEnd = lexer.next();
|
Lexeme interpEnd = lexer.next();
|
||||||
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
|
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
|
||||||
// The InterpStringEnd should start with }, not `.
|
// 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")
|
TEST_CASE("string_interpolation_full")
|
||||||
|
@ -177,7 +175,7 @@ TEST_CASE("string_interpolation_full")
|
||||||
Lexeme interpMid = lexer.next();
|
Lexeme interpMid = lexer.next();
|
||||||
CHECK_EQ(interpMid.type, Lexeme::InterpStringMid);
|
CHECK_EQ(interpMid.type, Lexeme::InterpStringMid);
|
||||||
CHECK_EQ(interpMid.toString(), "} {");
|
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();
|
Lexeme quote2 = lexer.next();
|
||||||
CHECK_EQ(quote2.type, Lexeme::QuotedString);
|
CHECK_EQ(quote2.type, Lexeme::QuotedString);
|
||||||
|
@ -186,7 +184,7 @@ TEST_CASE("string_interpolation_full")
|
||||||
Lexeme interpEnd = lexer.next();
|
Lexeme interpEnd = lexer.next();
|
||||||
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
|
CHECK_EQ(interpEnd.type, Lexeme::InterpStringEnd);
|
||||||
CHECK_EQ(interpEnd.toString(), "} end`");
|
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")
|
TEST_CASE("string_interpolation_double_brace")
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
|
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
|
||||||
LUAU_FASTFLAG(LuauNonStrictVisitorImprovements)
|
LUAU_FASTFLAG(LuauNonStrictVisitorImprovements)
|
||||||
|
LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix)
|
||||||
|
LUAU_FASTFLAG(LuauNormalizedBufferIsNotUnknown)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
@ -359,6 +361,23 @@ end
|
||||||
NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result);
|
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")
|
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_fn_produces_error")
|
||||||
{
|
{
|
||||||
CheckResult result = checkNonStrict(R"(
|
CheckResult result = checkNonStrict(R"(
|
||||||
|
@ -649,4 +668,17 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict")
|
||||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauFixNormalizedIntersectionOfNegatedClass)
|
|
||||||
LUAU_FASTFLAG(LuauNormalizeNegationFix)
|
LUAU_FASTFLAG(LuauNormalizeNegationFix)
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
@ -852,7 +851,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
|
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
|
||||||
{
|
{
|
||||||
ScopedFastFlag _{FFlag::LuauFixNormalizedIntersectionOfNegatedClass, true};
|
|
||||||
createSomeClasses(&frontend);
|
createSomeClasses(&frontend);
|
||||||
CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not<Child>) | Unrelated")));
|
CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not<Child>) | Unrelated")));
|
||||||
CHECK("((class & ~Child) | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<Child>")));
|
CHECK("((class & ~Child) | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<Child>")));
|
||||||
|
|
|
@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||||
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
||||||
|
LUAU_FASTFLAG(LuauParseOptionalAsNode)
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -3818,7 +3819,10 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
|
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")
|
TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty")
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauExtendedSimpleRequire)
|
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -182,8 +180,6 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_string_indexexpr")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_group")
|
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_group")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
|
|
||||||
|
|
||||||
AstStatBlock* block = parse(R"(
|
AstStatBlock* block = parse(R"(
|
||||||
local R = (((game).Test))
|
local R = (((game).Test))
|
||||||
require(R)
|
require(R)
|
||||||
|
@ -200,8 +196,6 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_group")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation")
|
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
|
|
||||||
|
|
||||||
AstStatBlock* block = parse(R"(
|
AstStatBlock* block = parse(R"(
|
||||||
local R = game.Test :: (typeof(game.Redirect))
|
local R = game.Test :: (typeof(game.Redirect))
|
||||||
require(R)
|
require(R)
|
||||||
|
@ -218,8 +212,6 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation_2")
|
TEST_CASE_FIXTURE(RequireTracerFixture, "follow_type_annotation_2")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauExtendedSimpleRequire{FFlag::LuauExtendedSimpleRequire, true};
|
|
||||||
|
|
||||||
AstStatBlock* block = parse(R"(
|
AstStatBlock* block = parse(R"(
|
||||||
local R = game.Test :: (typeof(game.Redirect))
|
local R = game.Test :: (typeof(game.Redirect))
|
||||||
local N = R.Nested
|
local N = R.Nested
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
|
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauNormalizedBufferIsNotUnknown)
|
||||||
|
|
||||||
using namespace Luau;
|
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_NOT_SUBTYPE(childClass, meet(builtinTypes->classType, negate(rootClass)));
|
||||||
TEST_IS_SUBTYPE(anotherChildClass, meet(builtinTypes->classType, negate(childClass)));
|
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")
|
TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class")
|
||||||
{
|
{
|
||||||
CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType);
|
CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType);
|
||||||
|
|
|
@ -5,14 +5,16 @@
|
||||||
|
|
||||||
#include "Fixture.h"
|
#include "Fixture.h"
|
||||||
|
|
||||||
|
#include "Luau/TypeChecker2.h"
|
||||||
#include "ScopedFlags.h"
|
#include "ScopedFlags.h"
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction)
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
LUAU_FASTFLAG(LuauAttributeSyntax)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("ToString");
|
TEST_SUITE_BEGIN("ToString");
|
||||||
|
|
||||||
|
@ -871,9 +873,28 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
std::string expected;
|
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 =
|
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)";
|
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
|
else
|
||||||
expected = R"(Type
|
expected = R"(Type
|
||||||
'{ a: number, b: string, c: { d: string } }'
|
'{ a: number, b: string, c: { d: string } }'
|
||||||
|
|
|
@ -15,7 +15,6 @@ using namespace Luau;
|
||||||
LUAU_FASTFLAG(LuauStoreCSTData)
|
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||||
LUAU_FASTFLAG(LuauAstTypeGroup3);
|
LUAU_FASTFLAG(LuauAstTypeGroup3);
|
||||||
LUAU_FASTFLAG(LexerFixInterpStringStart)
|
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TranspilerTests");
|
TEST_SUITE_BEGIN("TranspilerTests");
|
||||||
|
|
||||||
|
@ -304,6 +303,95 @@ TEST_CASE("function")
|
||||||
CHECK_EQ(two, transpile(two).code);
|
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")
|
TEST_CASE("returns_spaces_around_tokens")
|
||||||
{
|
{
|
||||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||||
|
@ -968,7 +1056,17 @@ TEST_CASE_FIXTURE(Fixture, "type_lists_should_be_emitted_correctly")
|
||||||
end
|
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)
|
local a:(string,number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1157,6 +1255,49 @@ local b: Packed<(number, string)>
|
||||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
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")
|
TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested")
|
||||||
{
|
{
|
||||||
std::string code = "local a: ((number)->(string))|((string)->(string))";
|
std::string code = "local a: ((number)->(string))|((string)->(string))";
|
||||||
|
@ -1488,7 +1629,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
|
||||||
{
|
{
|
||||||
ScopedFastFlag fflags[] = {
|
ScopedFastFlag fflags[] = {
|
||||||
{FFlag::LuauStoreCSTData, true},
|
{FFlag::LuauStoreCSTData, true},
|
||||||
{FFlag::LexerFixInterpStringStart, true},
|
|
||||||
};
|
};
|
||||||
std::string code = R"( local _ = `hello {name}` )";
|
std::string code = R"( local _ = `hello {name}` )";
|
||||||
|
|
||||||
|
@ -1499,7 +1639,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
|
||||||
{
|
{
|
||||||
ScopedFastFlag fflags[] = {
|
ScopedFastFlag fflags[] = {
|
||||||
{FFlag::LuauStoreCSTData, true},
|
{FFlag::LuauStoreCSTData, true},
|
||||||
{FFlag::LexerFixInterpStringStart, true},
|
|
||||||
};
|
};
|
||||||
std::string code = R"( local _ = `hello {
|
std::string code = R"( local _ = `hello {
|
||||||
name
|
name
|
||||||
|
@ -1512,7 +1651,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
|
||||||
{
|
{
|
||||||
ScopedFastFlag fflags[] = {
|
ScopedFastFlag fflags[] = {
|
||||||
{FFlag::LuauStoreCSTData, true},
|
{FFlag::LuauStoreCSTData, true},
|
||||||
{FFlag::LexerFixInterpStringStart, true},
|
|
||||||
};
|
};
|
||||||
std::string code = R"(
|
std::string code = R"(
|
||||||
error(
|
error(
|
||||||
|
@ -1536,7 +1674,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape")
|
||||||
{
|
{
|
||||||
ScopedFastFlag fflags[] = {
|
ScopedFastFlag fflags[] = {
|
||||||
{FFlag::LuauStoreCSTData, true},
|
{FFlag::LuauStoreCSTData, true},
|
||||||
{FFlag::LexerFixInterpStringStart, true},
|
|
||||||
};
|
};
|
||||||
std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )";
|
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);
|
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")
|
TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens")
|
||||||
{
|
{
|
||||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||||
|
@ -1752,4 +1905,122 @@ TEST_CASE("transpile_types_preserve_parentheses_style")
|
||||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -13,8 +13,11 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauIndexTypeFunctionImprovements)
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||||
|
LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods)
|
||||||
LUAU_FASTFLAG(LuauMetatableTypeFunctions)
|
LUAU_FASTFLAG(LuauMetatableTypeFunctions)
|
||||||
|
LUAU_FASTFLAG(LuauIndexAnyIsAny)
|
||||||
|
|
||||||
struct TypeFunctionFixture : Fixture
|
struct TypeFunctionFixture : Fixture
|
||||||
{
|
{
|
||||||
|
@ -904,6 +907,21 @@ end
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works")
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauSolverV2)
|
if (!FFlag::LuauSolverV2)
|
||||||
|
@ -965,6 +983,31 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_array")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_generic_types")
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauSolverV2)
|
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'");
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_var_indexer")
|
||||||
{
|
{
|
||||||
if (!FFlag::LuauSolverV2)
|
if (!FFlag::LuauSolverV2)
|
||||||
|
|
|
@ -7,13 +7,12 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauTypeFunFixHydratedClasses)
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||||
LUAU_FASTFLAG(LuauTypeFunSingletonEquality)
|
|
||||||
LUAU_FASTFLAG(LuauUserTypeFunTypeofReturnsType)
|
|
||||||
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
|
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
|
||||||
LUAU_FASTFLAG(LuauTypeFunPrintFix)
|
LUAU_FASTFLAG(LuauTypeFunPrintFix)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
|
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
|
||||||
|
|
||||||
|
@ -613,9 +612,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work")
|
||||||
ty:setreturns(nil, types.boolean) -- (string, number) -> (...boolean)
|
ty:setreturns(nil, types.boolean) -- (string, number) -> (...boolean)
|
||||||
if ty:is("function") then
|
if ty:is("function") then
|
||||||
-- creating a copy of `ty` parameters
|
-- creating a copy of `ty` parameters
|
||||||
local arr = {}
|
local arr: {type} = {}
|
||||||
for index, val in ty:parameters().head do
|
local args = ty:parameters().head
|
||||||
table.insert(arr, val)
|
if args then
|
||||||
|
for index, val in args do
|
||||||
|
table.insert(arr, val)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return types.newfunction({head = arr}, ty:returns()) -- (string, number) -> (...boolean)
|
return types.newfunction({head = arr}, ty:returns()) -- (string, number) -> (...boolean)
|
||||||
end
|
end
|
||||||
|
@ -648,7 +650,6 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works")
|
||||||
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works2")
|
TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works2")
|
||||||
{
|
{
|
||||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||||
ScopedFastFlag luauTypeFunFixHydratedClasses{FFlag::LuauTypeFunFixHydratedClasses, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type function serialize_class(arg)
|
type function serialize_class(arg)
|
||||||
|
@ -719,7 +720,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability")
|
||||||
readresult = types.boolean,
|
readresult = types.boolean,
|
||||||
writeresult = 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}
|
ty:setproperty(types.singleton("string"), types.number) -- {string: number, [number]: boolean}
|
||||||
local metatbl = types.newtable(nil, nil, ty) -- { { }, @metatable { [number]: boolean, string: number } }
|
local metatbl = types.newtable(nil, nil, ty) -- { { }, @metatable { [number]: boolean, string: number } }
|
||||||
-- mutate the table
|
-- mutate the table
|
||||||
|
@ -894,6 +895,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_type_overrides_eq_metamethod")
|
||||||
if p1 == p2 and t1 == t2 then
|
if p1 == p2 and t1 == t2 then
|
||||||
return types.number
|
return types.number
|
||||||
end
|
end
|
||||||
|
return types.unknown
|
||||||
end
|
end
|
||||||
local function ok(idx: hello<>): number return idx end
|
local function ok(idx: hello<>): number return idx end
|
||||||
)");
|
)");
|
||||||
|
@ -988,8 +990,37 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_3")
|
||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
if (FFlag::LuauUserTypeFunTypecheck)
|
||||||
CHECK(toString(result.errors[0]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)");
|
{
|
||||||
|
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")
|
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
|
local function ok2(idx: bar<'y'>): nil return idx end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// We are only checking first errors, others are mostly duplicates
|
if (FFlag::LuauUserTypeFunTypecheck)
|
||||||
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)");
|
// We are only checking first errors, others are mostly duplicates
|
||||||
CHECK(toString(result.errors[1]) == R"(Type function instance bar<"x"> is uninhabited)");
|
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")
|
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
|
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
|
if (FFlag::LuauUserTypeFunTypecheck)
|
||||||
UserDefinedTypeFunctionError* e = get<UserDefinedTypeFunctionError>(result.errors[0]);
|
{
|
||||||
REQUIRE(e);
|
// We are only checking first errors, others are mostly duplicates
|
||||||
CHECK(e->message == "'illegal' type function errored at runtime: [string \"illegal\"]:3: this function is not supported in type functions");
|
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")
|
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"(
|
CheckResult result = check(R"(
|
||||||
type function test(x)
|
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
|
end
|
||||||
local function ok(tbl: test<number>): never return tbl 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);
|
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)");
|
if (FFlag::LuauImproveTypePathsInErrors)
|
||||||
CHECK(toString(result.errors[2]) == R"(Type pack '"table"' could not be converted into 'never'; at [0], "table" is not a subtype of never)");
|
{
|
||||||
|
|
||||||
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_serialization")
|
||||||
|
@ -1293,8 +1375,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "implicit_export")
|
||||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||||
|
|
||||||
fileResolver.source["game/A"] = R"(
|
fileResolver.source["game/A"] = R"(
|
||||||
type function concat(a, b)
|
type function concat(a: type, b: type)
|
||||||
return types.singleton(a:value() .. b:value())
|
local as = a:value()
|
||||||
|
local bs = b:value()
|
||||||
|
assert(typeof(as) == "string")
|
||||||
|
assert(typeof(bs) == "string")
|
||||||
|
return types.singleton(as .. bs)
|
||||||
end
|
end
|
||||||
export type Concat<T, U> = concat<T, U>
|
export type Concat<T, U> = concat<T, U>
|
||||||
local a: concat<'first', 'second'>
|
local a: concat<'first', 'second'>
|
||||||
|
@ -1342,8 +1428,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "explicit_export")
|
||||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||||
|
|
||||||
fileResolver.source["game/A"] = R"(
|
fileResolver.source["game/A"] = R"(
|
||||||
export type function concat(a, b)
|
export type function concat(a: type, b: type)
|
||||||
return types.singleton(a:value() .. b:value())
|
local as = a:value()
|
||||||
|
local bs = b:value()
|
||||||
|
assert(typeof(as) == "string")
|
||||||
|
assert(typeof(bs) == "string")
|
||||||
|
return types.singleton(as .. bs)
|
||||||
end
|
end
|
||||||
local a: concat<'first', 'second'>
|
local a: concat<'first', 'second'>
|
||||||
return {}
|
return {}
|
||||||
|
@ -1892,7 +1982,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque")
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
|
||||||
{
|
{
|
||||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||||
ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type function compare(arg)
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string")
|
||||||
{
|
{
|
||||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||||
ScopedFastFlag luauTypeFunSingletonEquality{FFlag::LuauTypeFunSingletonEquality, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type function compare(arg)
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "typeof_type_userdata_returns_type")
|
||||||
{
|
{
|
||||||
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
|
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
|
||||||
ScopedFastFlag luauUserTypeFunTypeofReturnsType{FFlag::LuauUserTypeFunTypeofReturnsType, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type function test(t)
|
type function test(t)
|
||||||
|
@ -1982,4 +2069,38 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_parent_ops")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -11,6 +11,7 @@ using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
|
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeAliases");
|
TEST_SUITE_BEGIN("TypeAliases");
|
||||||
|
|
||||||
|
@ -216,7 +217,11 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(result.errors[0].location == Location{{4, 31}, {4, 44}});
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
CHECK_EQ(expected, toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
@ -236,7 +241,11 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
const std::string expected =
|
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(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
CHECK_EQ(expected, toString(result.errors[0]));
|
||||||
|
|
|
@ -12,7 +12,7 @@ using namespace Luau;
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauTableCloneClonesType3)
|
LUAU_FASTFLAG(LuauTableCloneClonesType3)
|
||||||
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
|
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
|
||||||
LUAU_FASTFLAG(LuauFreezeIgnorePersistent)
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("BuiltinTests");
|
TEST_SUITE_BEGIN("BuiltinTests");
|
||||||
|
|
||||||
|
@ -146,7 +146,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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'
|
'(number, number) -> boolean'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'((string, string) -> boolean)?'
|
'((string, string) -> boolean)?'
|
||||||
|
@ -985,7 +998,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(
|
CHECK_EQ(
|
||||||
"Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)",
|
"Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)",
|
||||||
toString(result.errors[0])
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_persistent_skip")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauFreezeIgnorePersistent{FFlag::LuauFreezeIgnorePersistent, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
table.freeze(table)
|
table.freeze(table)
|
||||||
)");
|
)");
|
||||||
|
@ -1267,8 +1285,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_persistent_skip")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_clone_persistent_skip")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "table_clone_persistent_skip")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauFreezeIgnorePersistent{FFlag::LuauFreezeIgnorePersistent, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
table.clone(table)
|
table.clone(table)
|
||||||
)");
|
)");
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
using std::nullopt;
|
using std::nullopt;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferClasses");
|
TEST_SUITE_BEGIN("TypeInferClasses");
|
||||||
|
|
||||||
|
@ -545,7 +546,13 @@ local b: B = a
|
||||||
|
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
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");
|
CHECK(toString(result.errors.at(0)) == "Type 'A' could not be converted into 'B'; at [read \"x\"], ChildClass is not exactly BaseClass");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,9 +9,7 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauClipNestedAndRecursiveUnion)
|
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauPreventReentrantTypeFunctionReduction)
|
|
||||||
LUAU_FASTFLAG(LuauDontForgetToReduceUnionFunc)
|
LUAU_FASTFLAG(LuauDontForgetToReduceUnionFunc)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("DefinitionTests");
|
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")
|
TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully")
|
||||||
{
|
{
|
||||||
ScopedFastFlag _{FFlag::LuauClipNestedAndRecursiveUnion, true};
|
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||||
local t: {[string]: string} = {}
|
local t: {[string]: string} = {}
|
||||||
|
|
||||||
|
@ -592,7 +588,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "vector3_overflow")
|
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.
|
// We set this to zero to ensure that we either run to completion or stack overflow here.
|
||||||
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
|
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,8 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||||
LUAU_FASTFLAG(LuauSubtypingFixTailPack)
|
|
||||||
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
|
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||||
|
|
||||||
|
@ -1306,7 +1306,16 @@ f(function(a, b, c, ...) return a + b end)
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
|
||||||
std::string expected;
|
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
|
expected = R"(Type
|
||||||
'<a>(number, number, a) -> number'
|
'<a>(number, number, a) -> number'
|
||||||
|
@ -1315,6 +1324,15 @@ could not be converted into
|
||||||
caused by:
|
caused by:
|
||||||
Argument count mismatch. Function expects 3 arguments, but only 2 are specified)";
|
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
|
else
|
||||||
{
|
{
|
||||||
expected = R"(Type
|
expected = R"(Type
|
||||||
|
@ -1519,7 +1537,14 @@ local b: B = a
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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'
|
'(number, number) -> string'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(number) -> string'
|
'(number) -> string'
|
||||||
|
@ -1542,7 +1567,14 @@ local b: B = a
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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'
|
'(number, number) -> string'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(number, string) -> string'
|
'(number, string) -> string'
|
||||||
|
@ -1566,7 +1598,13 @@ local b: B = a
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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'
|
'(number, number) -> number'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(number, number) -> (number, boolean)'
|
'(number, number) -> (number, boolean)'
|
||||||
|
@ -1589,7 +1627,14 @@ local b: B = a
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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'
|
'(number, number) -> string'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(number, number) -> number'
|
'(number, number) -> number'
|
||||||
|
@ -1613,7 +1658,14 @@ local b: B = a
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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)'
|
'(number, number) -> (number, string)'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(number, number) -> (number, boolean)'
|
'(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)"
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
|
@ -1812,15 +1882,31 @@ function t:b() return 2 end -- not OK
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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'
|
'(*error-type*) -> number'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'() -> number'
|
'() -> number'
|
||||||
caused by:
|
caused by:
|
||||||
Argument count mismatch. Function expects 1 argument, but none are specified)",
|
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")
|
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);
|
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)'
|
'(("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
|
could not be converted into
|
||||||
'("blue" | "red") -> ("blue" | "red") -> ("blue" | "red") -> false'; none of the intersection parts are compatible)";
|
'("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);
|
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");
|
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
|
else
|
||||||
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
|
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);
|
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");
|
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
|
else
|
||||||
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_wrap_result_call")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauSubtypingFixTailPack{FFlag::LuauSubtypingFixTailPack, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
function foo(a, b)
|
function foo(a, b)
|
||||||
coroutine.wrap(a)(b)
|
coroutine.wrap(a)(b)
|
||||||
|
|
|
@ -10,9 +10,10 @@
|
||||||
#include "ScopedFlags.h"
|
#include "ScopedFlags.h"
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
@ -875,7 +876,13 @@ y.a.c = y
|
||||||
CHECK(mismatch);
|
CHECK(mismatch);
|
||||||
CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T<string>?, d: number }, b: number }");
|
CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T<string>?, d: number }, b: number }");
|
||||||
CHECK_EQ(toString(mismatch->wantedType), "T<string>");
|
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);
|
CHECK_EQ(mismatch->reason, reason);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("IntersectionTypes");
|
TEST_SUITE_BEGIN("IntersectionTypes");
|
||||||
|
|
||||||
|
@ -357,12 +358,21 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
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'
|
'(string, number) -> string'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(string) -> string'
|
'(string) -> string'
|
||||||
caused by:
|
caused by:
|
||||||
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
|
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)";
|
||||||
|
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
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[1]), "Cannot add property 'z' to table 'X & Y'");
|
||||||
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
|
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);
|
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'
|
'(string, number) -> string'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(string) -> string'
|
'(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)
|
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[1] (Y)
|
||||||
type number (number) is not a subtype of X & Y & Z[2] (Z))";
|
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]));
|
CHECK_EQ(dcrExprected, toString(result.errors[0]));
|
||||||
else
|
else
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
CHECK_EQ(expected, toString(result.errors[0]));
|
||||||
|
@ -450,7 +480,23 @@ end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(
|
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)
|
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);
|
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(
|
CHECK_EQ(
|
||||||
R"(Type 'boolean & false' could not be converted into 'true'; type boolean & false[0] (boolean) is not a subtype of true (true)
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
// TODO: odd stringification of `false & (boolean & false)`.)
|
// 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(
|
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)
|
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)
|
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
|
local z : (number) -> number = x -- Not OK
|
||||||
end
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
const std::string expected1 = R"(Type
|
const std::string expected1 = R"(Type
|
||||||
|
@ -568,6 +671,15 @@ could not be converted into
|
||||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
@ -592,11 +704,23 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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)'
|
'((number) -> number) & ((string) -> string)'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(boolean | number) -> boolean | number'; none of the intersection parts are compatible)";
|
'(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")
|
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||||
|
@ -609,16 +733,42 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
const std::string expected =
|
|
||||||
(FFlag::LuauSolverV2)
|
if (FFlag::LuauSolverV2 && FFlag::LuauImproveTypePathsInErrors)
|
||||||
? 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))"
|
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
|
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? |}'
|
'{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'{| p: nil |}'; none of the intersection parts are compatible)";
|
'{| 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")
|
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
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK_EQ(
|
CHECK_EQ(
|
||||||
|
@ -646,6 +817,15 @@ could not be converted into
|
||||||
toString(result.errors[0])
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
@ -678,7 +858,52 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||||
end
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
CHECK_EQ(
|
CHECK_EQ(
|
||||||
|
@ -703,6 +928,17 @@ could not be converted into
|
||||||
toString(result.errors[1])
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
@ -730,6 +966,15 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
@ -757,6 +1002,15 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
@ -778,7 +1032,35 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
|
||||||
end
|
end
|
||||||
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);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
CHECK_EQ(
|
CHECK_EQ(
|
||||||
|
@ -798,6 +1080,15 @@ could not be converted into
|
||||||
toString(result.errors[1])
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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);
|
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)'
|
'((nil) -> unknown) & ((number) -> number)'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(number?) -> number?'; none of the intersection parts are compatible)";
|
'(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")
|
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);
|
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?)'
|
'((number) -> number?) & ((unknown) -> string?)'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(number?) -> nil'; none of the intersection parts are compatible)";
|
'(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")
|
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
||||||
|
@ -864,7 +1179,36 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
|
||||||
end
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
CHECK_EQ(
|
CHECK_EQ(
|
||||||
|
@ -885,6 +1229,15 @@ could not be converted into
|
||||||
toString(result.errors[1])
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
@ -907,7 +1260,40 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
|
||||||
end
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
const std::string expected1 = R"(Type
|
const std::string expected1 = R"(Type
|
||||||
|
@ -926,6 +1312,15 @@ could not be converted into
|
||||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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);
|
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)'
|
'((number?) -> (...number)) & ((string?) -> number | string)'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(number | string) -> (number, number?)'; none of the intersection parts are compatible)";
|
'(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")
|
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);
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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);
|
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(
|
CHECK_EQ(
|
||||||
R"(Type
|
R"(Type
|
||||||
|
@ -1056,6 +1486,16 @@ could not be converted into
|
||||||
toString(result.errors[0])
|
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
|
else
|
||||||
{
|
{
|
||||||
CHECK_EQ(
|
CHECK_EQ(
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
@ -461,7 +462,14 @@ local b: B.T = a
|
||||||
CheckResult result = frontend.check("game/C");
|
CheckResult result = frontend.check("game/C");
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(
|
CHECK(
|
||||||
toString(result.errors.at(0)) ==
|
toString(result.errors.at(0)) ==
|
||||||
|
@ -507,7 +515,14 @@ local b: B.T = a
|
||||||
CheckResult result = frontend.check("game/D");
|
CheckResult result = frontend.check("game/D");
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(
|
CHECK(
|
||||||
toString(result.errors.at(0)) ==
|
toString(result.errors.at(0)) ==
|
||||||
|
|
|
@ -79,4 +79,5 @@ end
|
||||||
)");
|
)");
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauDoNotGeneralizeInTypeFunctions)
|
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferOperators");
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "and_binexps_dont_unify")
|
||||||
{
|
{
|
||||||
ScopedFastFlag _{FFlag::LuauDoNotGeneralizeInTypeFunctions, true};
|
|
||||||
|
|
||||||
// `t` will be inferred to be of type `{ { test: unknown } }` which is
|
// `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
|
// reasonable, in that it's empty with no bounds on its members. Optimally
|
||||||
// we might emit an error here that the `print(...)` expression is
|
// we might emit an error here that the `print(...)` expression is
|
||||||
|
|
|
@ -11,14 +11,15 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification);
|
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||||
LUAU_FASTFLAG(LuauStoreCSTData);
|
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||||
LUAU_FASTINT(LuauNormalizeCacheLimit);
|
LUAU_FASTINT(LuauNormalizeCacheLimit)
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit);
|
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||||
LUAU_FASTINT(LuauTypeInferIterationLimit);
|
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
|
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("ProvisionalTests");
|
TEST_SUITE_BEGIN("ProvisionalTests");
|
||||||
|
|
||||||
|
@ -873,7 +874,15 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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? |}'
|
'{| x: number? |}'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'{| x: number |}'
|
'{| x: number |}'
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeSingletons");
|
TEST_SUITE_BEGIN("TypeSingletons");
|
||||||
|
|
||||||
|
@ -364,7 +365,16 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(
|
CHECK(
|
||||||
"Type\n"
|
"Type\n"
|
||||||
" '{ [\"\\n\"]: number }'\n"
|
" '{ [\"\\n\"]: number }'\n"
|
||||||
|
@ -440,12 +450,23 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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 }'
|
'{ result: string, success: boolean }'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'Err<number> | Ok<string>')";
|
'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")
|
TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options")
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
#include "Luau/Frontend.h"
|
#include "Luau/Frontend.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
|
#include "Luau/TypeChecker2.h"
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
|
||||||
|
@ -17,19 +18,18 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2)
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
||||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
||||||
LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
|
LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
|
||||||
LUAU_FASTFLAG(LuauDontInPlaceMutateTableType)
|
|
||||||
LUAU_FASTFLAG(LuauAllowNonSharedTableTypesInLiteral)
|
|
||||||
LUAU_FASTFLAG(LuauFollowTableFreeze)
|
LUAU_FASTFLAG(LuauFollowTableFreeze)
|
||||||
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
|
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
|
||||||
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||||
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
|
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
|
||||||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||||
LUAU_FASTFLAG(LuauSearchForRefineableType)
|
LUAU_FASTFLAG(LuauSearchForRefineableType)
|
||||||
LUAU_FASTFLAG(LuauDoNotGeneralizeInTypeFunctions)
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TableTests");
|
TEST_SUITE_BEGIN("TableTests");
|
||||||
|
|
||||||
|
@ -922,7 +922,16 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify")
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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
|
// CLI-114879 - Error path reporting is not great
|
||||||
CHECK(
|
CHECK(
|
||||||
|
@ -1805,7 +1814,16 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(
|
CHECK_EQ(
|
||||||
"Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }';"
|
"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);
|
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)");
|
CHECK(toString(result.errors.at(0)) == R"(Type 'A' could not be converted into 'B'; at [read "y"], number is not exactly string)");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2462,7 +2488,15 @@ local b: B = a
|
||||||
|
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
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)");
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -2489,7 +2523,17 @@ local b2 = setmetatable({ x = 2, y = 4 }, { __call = function(s, t) end });
|
||||||
local c2: typeof(a2) = b2
|
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:
|
caused by:
|
||||||
Type
|
Type
|
||||||
'{ x: number, y: string }'
|
'{ x: number, y: string }'
|
||||||
|
@ -2498,7 +2542,20 @@ could not be converted into
|
||||||
caused by:
|
caused by:
|
||||||
Property 'y' is not compatible.
|
Property 'y' is not compatible.
|
||||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
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:
|
caused by:
|
||||||
Type
|
Type
|
||||||
'{ __call: <a, b>(a, b) -> () }'
|
'{ __call: <a, b>(a, b) -> () }'
|
||||||
|
@ -2511,7 +2568,23 @@ Type
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'<a>(a) -> ()'; different number of generic type parameters)";
|
'<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
|
// The assignment of c2 to b2 is, surprisingly, allowed under the new
|
||||||
// solver for two reasons:
|
// solver for two reasons:
|
||||||
|
@ -2576,7 +2649,15 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
||||||
|
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
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]));
|
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);
|
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]));
|
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);
|
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(
|
CHECK(
|
||||||
"Type 'tmp' could not be converted into 'HasSuper'; at [read \"p\"], { x: number, y: number } is not exactly Super" ==
|
"Type 'tmp' could not be converted into 'HasSuper'; at [read \"p\"], { x: number, y: number } is not exactly Super" ==
|
||||||
toString(result.errors[0])
|
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 }
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK(
|
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("typeof(string)" == toString(tm4->givenType));
|
||||||
CHECK("t1 where t1 = { read absolutely_no_scalar_has_this_method: (t1) -> (a...) }" == toString(tm4->wantedType));
|
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
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
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);
|
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"
|
if (FFlag::LuauImproveTypePathsInErrors)
|
||||||
"\n\tat [read \"b\"], boolean is not exactly string"
|
{
|
||||||
"\n\tat [read \"c\"], number is not exactly boolean";
|
std::string expected =
|
||||||
CHECK(toString(result.errors[0]) == 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")
|
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);
|
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"
|
if (FFlag::LuauImproveTypePathsInErrors)
|
||||||
"\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)",
|
CHECK_EQ(
|
||||||
toString(result.errors[0])
|
"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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
|
||||||
|
@ -5071,7 +5235,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_in_literal")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs[] = {
|
ScopedFastFlag sffs[] = {
|
||||||
{FFlag::LuauSolverV2, true},
|
{FFlag::LuauSolverV2, true},
|
||||||
{FFlag::LuauDontInPlaceMutateTableType, true},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto result = check(R"(
|
auto result = check(R"(
|
||||||
|
@ -5098,11 +5261,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_in_literal")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_from_fuzzer")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_from_fuzzer")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs[] = {
|
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||||
{FFlag::LuauSolverV2, true},
|
|
||||||
{FFlag::LuauDontInPlaceMutateTableType, true},
|
|
||||||
{FFlag::LuauAllowNonSharedTableTypesInLiteral, true},
|
|
||||||
};
|
|
||||||
|
|
||||||
// This would trigger an assert previously, so we really only care that
|
// This would trigger an assert previously, so we really only care that
|
||||||
// there are errors (and there will be: lots of syntax errors).
|
// 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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "write_only_table_field_duplicate")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs[] = {
|
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||||
{FFlag::LuauSolverV2, true},
|
|
||||||
{FFlag::LuauDontInPlaceMutateTableType, true},
|
|
||||||
{FFlag::LuauAllowNonSharedTableTypesInLiteral, true},
|
|
||||||
};
|
|
||||||
|
|
||||||
auto result = check(R"(
|
auto result = check(R"(
|
||||||
type WriteOnlyTable = { write x: number }
|
type WriteOnlyTable = { write x: number }
|
||||||
|
@ -5345,7 +5500,6 @@ TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sffs[] = {
|
ScopedFastFlag sffs[] = {
|
||||||
{FFlag::LuauSolverV2, true},
|
{FFlag::LuauSolverV2, true},
|
||||||
{FFlag::LuauDoNotGeneralizeInTypeFunctions, true},
|
|
||||||
{FFlag::LuauSearchForRefineableType, true},
|
{FFlag::LuauSearchForRefineableType, true},
|
||||||
{FFlag::DebugLuauAssertOnForcedConstraint, 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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -29,6 +29,7 @@ LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
|
||||||
LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments)
|
LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments)
|
||||||
LUAU_FASTFLAG(LuauUnifyMetatableWithAny)
|
LUAU_FASTFLAG(LuauUnifyMetatableWithAny)
|
||||||
LUAU_FASTFLAG(LuauExtraFollows)
|
LUAU_FASTFLAG(LuauExtraFollows)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
@ -1134,7 +1135,29 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
||||||
end
|
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.
|
// 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
|
// 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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -9,8 +9,10 @@
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
LUAU_FASTFLAG(LuauSolverV2);
|
LUAU_FASTFLAG(LuauSolverV2)
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
|
||||||
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypePackTests");
|
TEST_SUITE_BEGIN("TypePackTests");
|
||||||
|
|
||||||
|
@ -951,7 +953,19 @@ a = b
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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"
|
const std::string expected = "Type\n"
|
||||||
" '() -> (number, ...boolean)'\n"
|
" '() -> (number, ...boolean)'\n"
|
||||||
|
@ -960,6 +974,16 @@ a = b
|
||||||
|
|
||||||
CHECK(expected == toString(result.errors[0]));
|
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
|
else
|
||||||
{
|
{
|
||||||
const std::string expected = R"(Type
|
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);
|
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(
|
CHECK(
|
||||||
toString(result.errors.at(0)) ==
|
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)"
|
"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(LuauSolverV2)
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("UnionTypes");
|
TEST_SUITE_BEGIN("UnionTypes");
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "fuzzer_union_with_one_part_assertion")
|
TEST_CASE_FIXTURE(Fixture, "fuzzer_union_with_one_part_assertion")
|
||||||
|
@ -538,7 +540,19 @@ end
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(
|
CHECK_EQ(
|
||||||
toString(result.errors[0]),
|
toString(result.errors[0]),
|
||||||
|
@ -667,11 +681,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
// NOTE: union normalization will improve this message
|
// 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'
|
'(string) -> number'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'((number) -> string) | ((number) -> string)'; none of the union options are compatible)";
|
'((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")
|
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);
|
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...)'
|
'(number, a...) -> (number?, a...)'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible)";
|
'((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")
|
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);
|
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?'
|
'(number) -> number?'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible)";
|
'((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")
|
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);
|
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'
|
'() -> number | string'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(() -> (string, string)) | (() -> number)'; none of the union options are compatible)";
|
'(() -> (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")
|
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);
|
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?)'
|
'(...nil) -> (...number?)'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible)";
|
'((...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")
|
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);
|
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
|
CHECK(R"(Type
|
||||||
'(number) -> ()'
|
'(number) -> ()'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'((...number?) -> ()) | ((number?) -> ())')" == toString(result.errors[0]));
|
'((...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
|
else
|
||||||
{
|
{
|
||||||
const std::string expected = R"(Type
|
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);
|
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)'
|
'() -> (number?, ...number)'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'(() -> (...number)) | (() -> number)'; none of the union options are compatible)";
|
'(() -> (...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")
|
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);
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -554,6 +554,14 @@ TEST_CASE("chain")
|
||||||
CHECK(toString(PathBuilder().index(0).mt().build()) == "[0].metatable()");
|
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_END(); // TypePathToString
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypePathBuilder");
|
TEST_SUITE_BEGIN("TypePathBuilder");
|
||||||
|
|
Loading…
Add table
Reference in a new issue