mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 21:10:37 +00:00
Sync to upstream/release/555
This commit is contained in:
parent
f52169509c
commit
fc459699da
72 changed files with 2537 additions and 1084 deletions
|
@ -40,6 +40,7 @@ TypeId makeFunction( // Polymorphic
|
|||
|
||||
void attachMagicFunction(TypeId ty, MagicFunction fn);
|
||||
void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn);
|
||||
void attachDcrMagicRefinement(TypeId ty, DcrMagicRefinement fn);
|
||||
|
||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol = std::nullopt);
|
||||
void assignPropDocumentationSymbols(TableTypeVar::Props& props, const std::string& baseName);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "Luau/Def.h"
|
||||
#include "Luau/TypedAllocator.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <memory>
|
||||
|
@ -11,6 +10,9 @@
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeVar;
|
||||
using TypeId = const TypeVar*;
|
||||
|
||||
struct Negation;
|
||||
struct Conjunction;
|
||||
struct Disjunction;
|
||||
|
|
|
@ -149,11 +149,15 @@ struct SetPropConstraint
|
|||
TypeId propType;
|
||||
};
|
||||
|
||||
// result ~ if isSingleton D then ~D else unknown where D = discriminantType
|
||||
// if negation:
|
||||
// result ~ if isSingleton D then ~D else unknown where D = discriminantType
|
||||
// if not negation:
|
||||
// result ~ if isSingleton D then D else unknown where D = discriminantType
|
||||
struct SingletonOrTopTypeConstraint
|
||||
{
|
||||
TypeId resultType;
|
||||
TypeId discriminantType;
|
||||
bool negated;
|
||||
};
|
||||
|
||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "Luau/Ast.h"
|
||||
#include "Luau/Connective.h"
|
||||
#include "Luau/Constraint.h"
|
||||
#include "Luau/DataFlowGraphBuilder.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/NotNull.h"
|
||||
|
@ -215,7 +215,7 @@ struct ConstraintGraphBuilder
|
|||
ScopePtr bodyScope;
|
||||
};
|
||||
|
||||
FunctionSignature checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn);
|
||||
FunctionSignature checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType = {});
|
||||
|
||||
/**
|
||||
* Checks the body of a function expression.
|
||||
|
|
|
@ -69,9 +69,14 @@ private:
|
|||
struct InternalErrorReporter* handle;
|
||||
std::vector<std::unique_ptr<DfgScope>> scopes;
|
||||
|
||||
// Does not belong in DataFlowGraphBuilder, but the old solver allows properties to escape the scope they were defined in,
|
||||
// so we will need to be able to emulate this same behavior here too. We can kill this once we have better flow sensitivity.
|
||||
DenseHashMap<const Def*, std::unordered_map<std::string, const Def*>> props{nullptr};
|
||||
|
||||
DfgScope* childScope(DfgScope* scope);
|
||||
|
||||
std::optional<DefId> use(DfgScope* scope, Symbol symbol, AstExpr* e);
|
||||
DefId use(DefId def, AstExprIndexName* e);
|
||||
|
||||
void visit(DfgScope* scope, AstStatBlock* b);
|
||||
void visitBlockWithoutChildScope(DfgScope* scope, AstStatBlock* b);
|
|
@ -5,37 +5,38 @@
|
|||
#include "Luau/TypedAllocator.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using Def = Variant<struct Undefined, struct Phi>;
|
||||
|
||||
/**
|
||||
* We statically approximate a value at runtime using a symbolic value, which we call a Def.
|
||||
*
|
||||
* DataFlowGraphBuilder will allocate these defs as a stand-in for some Luau values, and bind them to places that
|
||||
* can hold a Luau value, and then observes how those defs will commute as it statically evaluate the program.
|
||||
*
|
||||
* It must also be noted that defs are a cyclic graph, so it is not safe to recursively traverse into it expecting it to terminate.
|
||||
*/
|
||||
struct Def;
|
||||
using DefId = NotNull<const Def>;
|
||||
|
||||
struct FieldMetadata
|
||||
{
|
||||
DefId parent;
|
||||
std::string propName;
|
||||
};
|
||||
|
||||
/**
|
||||
* A "single-object" value.
|
||||
* A cell is a "single-object" value.
|
||||
*
|
||||
* Leaky implementation note: sometimes "multiple-object" values, but none of which were interesting enough to warrant creating a phi node instead.
|
||||
* That can happen because there's no point in creating a phi node that points to either resultant in `if math.random() > 0.5 then 5 else "hello"`.
|
||||
* This might become of utmost importance if we wanted to do some backward reasoning, e.g. if `5` is taken, then `cond` must be `truthy`.
|
||||
*/
|
||||
struct Undefined
|
||||
struct Cell
|
||||
{
|
||||
std::optional<struct FieldMetadata> field;
|
||||
};
|
||||
|
||||
/**
|
||||
* A phi node is a union of defs.
|
||||
* A phi node is a union of cells.
|
||||
*
|
||||
* We need this because we're statically evaluating a program, and sometimes a place may be assigned with
|
||||
* different defs, and when that happens, we need a special data type that merges in all the defs
|
||||
* different cells, and when that happens, we need a special data type that merges in all the cells
|
||||
* that will flow into that specific place. For example, consider this simple program:
|
||||
*
|
||||
* ```
|
||||
|
@ -56,23 +57,35 @@ struct Phi
|
|||
std::vector<DefId> operands;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(DefId def)
|
||||
/**
|
||||
* We statically approximate a value at runtime using a symbolic value, which we call a Def.
|
||||
*
|
||||
* DataFlowGraphBuilder will allocate these defs as a stand-in for some Luau values, and bind them to places that
|
||||
* can hold a Luau value, and then observes how those defs will commute as it statically evaluate the program.
|
||||
*
|
||||
* It must also be noted that defs are a cyclic graph, so it is not safe to recursively traverse into it expecting it to terminate.
|
||||
*/
|
||||
struct Def
|
||||
{
|
||||
return get_if<T>(def.get());
|
||||
}
|
||||
using V = Variant<struct Cell, struct Phi>;
|
||||
|
||||
V v;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const T* get(DefId def)
|
||||
{
|
||||
return getMutable<T>(def);
|
||||
return get_if<T>(&def->v);
|
||||
}
|
||||
|
||||
struct DefArena
|
||||
{
|
||||
TypedAllocator<Def> allocator;
|
||||
|
||||
DefId freshDef();
|
||||
DefId freshCell();
|
||||
DefId freshCell(DefId parent, const std::string& prop);
|
||||
// TODO: implement once we have cases where we need to merge in definitions
|
||||
// DefId phi(const std::vector<DefId>& defs);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -7,21 +7,31 @@
|
|||
#include "Luau/Variant.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauIceExceptionInheritanceChange)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
struct TypeError;
|
||||
|
||||
|
||||
struct TypeMismatch
|
||||
{
|
||||
enum Context
|
||||
{
|
||||
CovariantContext,
|
||||
InvariantContext
|
||||
};
|
||||
|
||||
TypeMismatch() = default;
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, std::optional<TypeError> error);
|
||||
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, Context context);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, Context context);
|
||||
TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, std::optional<TypeError> error, Context context);
|
||||
|
||||
TypeId wantedType = nullptr;
|
||||
TypeId givenType = nullptr;
|
||||
Context context = CovariantContext;
|
||||
|
||||
std::string reason;
|
||||
std::shared_ptr<TypeError> error;
|
||||
|
@ -312,12 +322,33 @@ struct TypePackMismatch
|
|||
bool operator==(const TypePackMismatch& rhs) const;
|
||||
};
|
||||
|
||||
struct DynamicPropertyLookupOnClassesUnsafe
|
||||
{
|
||||
TypeId ty;
|
||||
|
||||
bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const;
|
||||
};
|
||||
|
||||
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
|
||||
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
|
||||
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
|
||||
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
|
||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
|
||||
TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch>;
|
||||
TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe>;
|
||||
|
||||
struct TypeErrorSummary
|
||||
{
|
||||
Location location;
|
||||
ModuleName moduleName;
|
||||
int code;
|
||||
|
||||
TypeErrorSummary(const Location& location, const ModuleName& moduleName, int code)
|
||||
: location(location)
|
||||
, moduleName(moduleName)
|
||||
, code(code)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeError
|
||||
{
|
||||
|
@ -325,6 +356,7 @@ struct TypeError
|
|||
ModuleName moduleName;
|
||||
TypeErrorData data;
|
||||
|
||||
static int minCode();
|
||||
int code() const;
|
||||
|
||||
TypeError() = default;
|
||||
|
@ -342,6 +374,8 @@ struct TypeError
|
|||
}
|
||||
|
||||
bool operator==(const TypeError& rhs) const;
|
||||
|
||||
TypeErrorSummary summary() const;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
@ -406,10 +440,4 @@ public:
|
|||
const std::optional<Location> location;
|
||||
};
|
||||
|
||||
// These two function overloads only exist to facilitate fast flagging a change to InternalCompilerError
|
||||
// Both functions can be removed when FFlagLuauIceExceptionInheritanceChange is removed and calling code
|
||||
// can directly throw InternalCompilerError.
|
||||
[[noreturn]] void throwRuntimeError(const std::string& message);
|
||||
[[noreturn]] void throwRuntimeError(const std::string& message, const std::string& moduleName);
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -194,6 +194,8 @@ struct NormalizedFunctionType
|
|||
struct NormalizedType;
|
||||
using NormalizedTyvars = std::unordered_map<TypeId, std::unique_ptr<NormalizedType>>;
|
||||
|
||||
bool isInhabited_DEPRECATED(const NormalizedType& norm);
|
||||
|
||||
// A normalized type is either any, unknown, or one of the form P | T | F | G where
|
||||
// * P is a union of primitive types (including singletons, classes and the error type)
|
||||
// * T is a union of table types
|
||||
|
@ -328,6 +330,10 @@ public:
|
|||
bool intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1);
|
||||
bool intersectNormalWithTy(NormalizedType& here, TypeId there);
|
||||
|
||||
// Check for inhabitance
|
||||
bool isInhabited(TypeId ty, std::unordered_set<TypeId> seen = {});
|
||||
bool isInhabited(const NormalizedType* norm, std::unordered_set<TypeId> seen = {});
|
||||
|
||||
// -------- Convert back from a normalized type to a type
|
||||
TypeId typeFromNormal(const NormalizedType& norm);
|
||||
};
|
||||
|
|
|
@ -59,6 +59,20 @@ struct NotNull
|
|||
return ptr;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool operator==(NotNull<U> other) const noexcept
|
||||
{
|
||||
return get() == other.get();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
bool operator!=(NotNull<U> other) const noexcept
|
||||
{
|
||||
return get() != other.get();
|
||||
}
|
||||
|
||||
operator bool() const noexcept = delete;
|
||||
|
||||
T& operator[](int) = delete;
|
||||
|
||||
T& operator+(int) = delete;
|
||||
|
|
|
@ -15,16 +15,6 @@ struct RecursionLimitException : public InternalCompilerError
|
|||
RecursionLimitException()
|
||||
: InternalCompilerError("Internal recursion counter limit exceeded")
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauIceExceptionInheritanceChange);
|
||||
}
|
||||
};
|
||||
|
||||
struct RecursionLimitException_DEPRECATED : public std::exception
|
||||
{
|
||||
const char* what() const noexcept
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauIceExceptionInheritanceChange);
|
||||
return "Internal recursion counter limit exceeded";
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -53,14 +43,7 @@ struct RecursionLimiter : RecursionCounter
|
|||
{
|
||||
if (limit > 0 && *count > limit)
|
||||
{
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
throw RecursionLimitException();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw RecursionLimitException_DEPRECATED();
|
||||
}
|
||||
throw RecursionLimitException();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -30,7 +30,7 @@ struct ToStringOptions
|
|||
bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}'
|
||||
bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level.
|
||||
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
|
||||
bool indent = false;
|
||||
bool DEPRECATED_indent = false; // TODO Deprecated field, prune when clipping flag FFlagLuauLineBreaksDeterminIndents
|
||||
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypeVars
|
||||
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
|
||||
ToStringNameMap nameMap;
|
||||
|
@ -90,8 +90,6 @@ inline std::string toString(const Constraint& c)
|
|||
return toString(c, ToStringOptions{});
|
||||
}
|
||||
|
||||
std::string toString(const LValue& lvalue);
|
||||
|
||||
std::string toString(const TypeVar& tv, ToStringOptions& opts);
|
||||
std::string toString(const TypePackVar& tp, ToStringOptions& opts);
|
||||
|
||||
|
|
|
@ -108,6 +108,8 @@ struct TxnLog
|
|||
// If both logs talk about the same type, pack, or table, the rhs takes
|
||||
// priority.
|
||||
void concat(TxnLog rhs);
|
||||
void concatAsIntersections(TxnLog rhs, NotNull<TypeArena> arena);
|
||||
void concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena);
|
||||
|
||||
// Commits the TxnLog, rebinding all type pointers to their pending states.
|
||||
// Clears the TxnLog afterwards.
|
||||
|
|
|
@ -54,16 +54,9 @@ public:
|
|||
explicit TimeLimitError(const std::string& moduleName)
|
||||
: InternalCompilerError("Typeinfer failed to complete in allotted time", moduleName)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauIceExceptionInheritanceChange);
|
||||
}
|
||||
};
|
||||
|
||||
class TimeLimitError_DEPRECATED : public std::exception
|
||||
{
|
||||
public:
|
||||
virtual const char* what() const throw();
|
||||
};
|
||||
|
||||
// All TypeVars are retained via Environment::typeVars. All TypeIds
|
||||
// within a program are borrowed pointers into this set.
|
||||
struct TypeChecker
|
||||
|
@ -95,6 +88,7 @@ struct TypeChecker
|
|||
void check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction);
|
||||
|
||||
void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0);
|
||||
void prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass);
|
||||
|
||||
void checkBlock(const ScopePtr& scope, const AstStatBlock& statement);
|
||||
void checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement);
|
||||
|
@ -399,6 +393,11 @@ private:
|
|||
*/
|
||||
DenseHashSet<std::pair<bool, Name>, HashBoolNamePair> duplicateTypeAliases;
|
||||
|
||||
/**
|
||||
* A set of incorrect class definitions which is used to avoid a second-pass analysis.
|
||||
*/
|
||||
DenseHashSet<const AstStatDeclareClass*> incorrectClassDefinitions{nullptr};
|
||||
|
||||
std::vector<std::pair<TypeId, ScopePtr>> deferredQuantification;
|
||||
};
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
|
|||
// Returns the minimum and maximum number of types the argument list can accept.
|
||||
std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp, bool includeHiddenVariadics = false);
|
||||
|
||||
// "Render" a type pack out to an array of a given length. Expands variadics and
|
||||
// various other things to get there.
|
||||
std::vector<TypeId> flatten(TypeArena& arena, NotNull<SingletonTypes> singletonTypes, TypePackId pack, size_t length);
|
||||
// Extend the provided pack to at least `length` types.
|
||||
// Returns a temporary TypePack that contains those types plus a tail.
|
||||
TypePack extendTypePack(TypeArena& arena, NotNull<SingletonTypes> singletonTypes, TypePackId pack, size_t length);
|
||||
|
||||
/**
|
||||
* Reduces a union by decomposing to the any/error type if it appears in the
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Connective.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Def.h"
|
||||
#include "Luau/NotNull.h"
|
||||
|
@ -257,7 +259,17 @@ struct MagicFunctionCallContext
|
|||
TypePackId result;
|
||||
};
|
||||
|
||||
using DcrMagicFunction = std::function<bool(MagicFunctionCallContext)>;
|
||||
using DcrMagicFunction = bool (*)(MagicFunctionCallContext);
|
||||
|
||||
struct MagicRefinementContext
|
||||
{
|
||||
ScopePtr scope;
|
||||
NotNull<const DataFlowGraph> dfg;
|
||||
NotNull<ConnectiveArena> connectiveArena;
|
||||
const class AstExprCall* callSite;
|
||||
};
|
||||
|
||||
using DcrMagicRefinement = std::vector<ConnectiveId> (*)(MagicRefinementContext);
|
||||
|
||||
struct FunctionTypeVar
|
||||
{
|
||||
|
@ -279,19 +291,20 @@ struct FunctionTypeVar
|
|||
FunctionTypeVar(TypeLevel level, Scope* scope, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes,
|
||||
TypePackId retTypes, std::optional<FunctionDefinition> defn = {}, bool hasSelf = false);
|
||||
|
||||
TypeLevel level;
|
||||
Scope* scope = nullptr;
|
||||
std::optional<FunctionDefinition> definition;
|
||||
/// These should all be generic
|
||||
std::vector<TypeId> generics;
|
||||
std::vector<TypePackId> genericPacks;
|
||||
TypePackId argTypes;
|
||||
std::vector<std::optional<FunctionArgument>> argNames;
|
||||
TypePackId retTypes;
|
||||
std::optional<FunctionDefinition> definition;
|
||||
MagicFunction magicFunction = nullptr; // Function pointer, can be nullptr.
|
||||
DcrMagicFunction dcrMagicFunction = nullptr; // can be nullptr
|
||||
bool hasSelf;
|
||||
Tags tags;
|
||||
TypeLevel level;
|
||||
Scope* scope = nullptr;
|
||||
TypePackId argTypes;
|
||||
TypePackId retTypes;
|
||||
MagicFunction magicFunction = nullptr;
|
||||
DcrMagicFunction dcrMagicFunction = nullptr; // Fired only while solving constraints
|
||||
DcrMagicRefinement dcrMagicRefinement = nullptr; // Fired only while generating constraints
|
||||
bool hasSelf;
|
||||
bool hasNoGenerics = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -120,6 +120,9 @@ private:
|
|||
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(TypeId lhsType, Name name);
|
||||
|
||||
TxnLog combineLogsIntoIntersection(std::vector<TxnLog> logs);
|
||||
TxnLog combineLogsIntoUnion(std::vector<TxnLog> logs);
|
||||
|
||||
public:
|
||||
// Returns true if the type "needle" already occurs within "haystack" and reports an "infinite type error"
|
||||
bool occursCheck(TypeId needle, TypeId haystack);
|
||||
|
@ -134,6 +137,7 @@ public:
|
|||
|
||||
private:
|
||||
bool isNonstrictMode() const;
|
||||
TypeMismatch::Context mismatchContext();
|
||||
|
||||
void checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, TypeId wantedType, TypeId givenType);
|
||||
void checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const std::string& prop, TypeId wantedType, TypeId givenType);
|
||||
|
|
|
@ -11,15 +11,12 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCheckOverloadedDocSymbol, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
||||
struct AutocompleteNodeFinder : public AstVisitor
|
||||
{
|
||||
const Position pos;
|
||||
|
@ -432,8 +429,6 @@ ExprOrLocal findExprOrLocalAtPosition(const SourceModule& source, Position pos)
|
|||
static std::optional<DocumentationSymbol> checkOverloadedDocumentationSymbol(
|
||||
const Module& module, const TypeId ty, const AstExpr* parentExpr, const std::optional<DocumentationSymbol> documentationSymbol)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCheckOverloadedDocSymbol);
|
||||
|
||||
if (!documentationSymbol)
|
||||
return std::nullopt;
|
||||
|
||||
|
@ -469,40 +464,7 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
|
|||
AstExpr* parentExpr = ancestry.size() >= 2 ? ancestry[ancestry.size() - 2]->asExpr() : nullptr;
|
||||
|
||||
if (std::optional<Binding> binding = findBindingAtPosition(module, source, position))
|
||||
{
|
||||
if (FFlag::LuauCheckOverloadedDocSymbol)
|
||||
{
|
||||
return checkOverloadedDocumentationSymbol(module, binding->typeId, parentExpr, binding->documentationSymbol);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (binding->documentationSymbol)
|
||||
{
|
||||
// This might be an overloaded function binding.
|
||||
if (get<IntersectionTypeVar>(follow(binding->typeId)))
|
||||
{
|
||||
TypeId matchingOverload = nullptr;
|
||||
if (parentExpr && parentExpr->is<AstExprCall>())
|
||||
{
|
||||
if (auto it = module.astOverloadResolvedTypes.find(parentExpr))
|
||||
{
|
||||
matchingOverload = *it;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingOverload)
|
||||
{
|
||||
std::string overloadSymbol = *binding->documentationSymbol + "/overload/";
|
||||
// Default toString options are fine for this purpose.
|
||||
overloadSymbol += toString(matchingOverload);
|
||||
return overloadSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binding->documentationSymbol;
|
||||
}
|
||||
}
|
||||
return checkOverloadedDocumentationSymbol(module, binding->typeId, parentExpr, binding->documentationSymbol);
|
||||
|
||||
if (targetExpr)
|
||||
{
|
||||
|
@ -514,22 +476,12 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
|
|||
if (const TableTypeVar* ttv = get<TableTypeVar>(parentTy))
|
||||
{
|
||||
if (auto propIt = ttv->props.find(indexName->index.value); propIt != ttv->props.end())
|
||||
{
|
||||
if (FFlag::LuauCheckOverloadedDocSymbol)
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type, parentExpr, propIt->second.documentationSymbol);
|
||||
else
|
||||
return propIt->second.documentationSymbol;
|
||||
}
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type, parentExpr, propIt->second.documentationSymbol);
|
||||
}
|
||||
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(parentTy))
|
||||
{
|
||||
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
|
||||
{
|
||||
if (FFlag::LuauCheckOverloadedDocSymbol)
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type, parentExpr, propIt->second.documentationSymbol);
|
||||
else
|
||||
return propIt->second.documentationSymbol;
|
||||
}
|
||||
return checkOverloadedDocumentationSymbol(module, propIt->second.type, parentExpr, propIt->second.documentationSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1457,7 +1457,8 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
|||
return autocompleteExpression(sourceModule, *module, singletonTypes, &typeArena, ancestry, position);
|
||||
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>() || node->is<AstExprInterpString>()))
|
||||
else if (AstExprTable* exprTable = parent->as<AstExprTable>();
|
||||
exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>() || node->is<AstExprInterpString>()))
|
||||
{
|
||||
for (const auto& [kind, key, value] : exprTable->items)
|
||||
{
|
||||
|
|
|
@ -132,6 +132,14 @@ void attachDcrMagicFunction(TypeId ty, DcrMagicFunction fn)
|
|||
LUAU_ASSERT(!"Got a non functional type");
|
||||
}
|
||||
|
||||
void attachDcrMagicRefinement(TypeId ty, DcrMagicRefinement fn)
|
||||
{
|
||||
if (auto ftv = getMutable<FunctionTypeVar>(ty))
|
||||
ftv->dcrMagicRefinement = fn;
|
||||
else
|
||||
LUAU_ASSERT(!"Got a non functional type");
|
||||
}
|
||||
|
||||
Property makeProperty(TypeId ty, std::optional<std::string> documentationSymbol)
|
||||
{
|
||||
return {
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
|
@ -191,7 +193,7 @@ static void unionRefinements(const std::unordered_map<DefId, TypeId>& lhs, const
|
|||
}
|
||||
|
||||
static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, std::unordered_map<DefId, TypeId>* refis, bool sense,
|
||||
NotNull<TypeArena> arena, bool eq, std::vector<SingletonOrTopTypeConstraint>* constraints)
|
||||
NotNull<TypeArena> arena, bool eq, std::vector<ConstraintV>* constraints)
|
||||
{
|
||||
using RefinementMap = std::unordered_map<DefId, TypeId>;
|
||||
|
||||
|
@ -231,10 +233,10 @@ static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, st
|
|||
TypeId discriminantTy = proposition->discriminantTy;
|
||||
if (!sense && !eq)
|
||||
discriminantTy = arena->addType(NegationTypeVar{proposition->discriminantTy});
|
||||
else if (!sense && eq)
|
||||
else if (eq)
|
||||
{
|
||||
discriminantTy = arena->addType(BlockedTypeVar{});
|
||||
constraints->push_back(SingletonOrTopTypeConstraint{discriminantTy, proposition->discriminantTy});
|
||||
constraints->push_back(SingletonOrTopTypeConstraint{discriminantTy, proposition->discriminantTy, !sense});
|
||||
}
|
||||
|
||||
if (auto it = refis->find(proposition->def); it != refis->end())
|
||||
|
@ -244,23 +246,43 @@ static void computeRefinement(const ScopePtr& scope, ConnectiveId connective, st
|
|||
}
|
||||
}
|
||||
|
||||
static std::pair<DefId, TypeId> computeDiscriminantType(NotNull<TypeArena> arena, const ScopePtr& scope, DefId def, TypeId discriminantTy)
|
||||
{
|
||||
LUAU_ASSERT(get<Cell>(def));
|
||||
|
||||
while (const Cell* current = get<Cell>(def))
|
||||
{
|
||||
if (!current->field)
|
||||
break;
|
||||
|
||||
TableTypeVar::Props props{{current->field->propName, Property{discriminantTy}}};
|
||||
discriminantTy = arena->addType(TableTypeVar{std::move(props), std::nullopt, TypeLevel{}, scope.get(), TableState::Sealed});
|
||||
|
||||
def = current->field->parent;
|
||||
current = get<Cell>(def);
|
||||
}
|
||||
|
||||
return {def, discriminantTy};
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, ConnectiveId connective)
|
||||
{
|
||||
if (!connective)
|
||||
return;
|
||||
|
||||
std::unordered_map<DefId, TypeId> refinements;
|
||||
std::vector<SingletonOrTopTypeConstraint> constraints;
|
||||
std::vector<ConstraintV> constraints;
|
||||
computeRefinement(scope, connective, &refinements, /*sense*/ true, arena, /*eq*/ false, &constraints);
|
||||
|
||||
for (auto [def, discriminantTy] : refinements)
|
||||
{
|
||||
std::optional<TypeId> defTy = scope->lookup(def);
|
||||
auto [def2, discriminantTy2] = computeDiscriminantType(arena, scope, def, discriminantTy);
|
||||
std::optional<TypeId> defTy = scope->lookup(def2);
|
||||
if (!defTy)
|
||||
ice->ice("Every DefId must map to a type!");
|
||||
|
||||
TypeId resultTy = arena->addType(IntersectionTypeVar{{*defTy, discriminantTy}});
|
||||
scope->dcrRefinements[def] = resultTy;
|
||||
TypeId resultTy = arena->addType(IntersectionTypeVar{{*defTy, discriminantTy2}});
|
||||
scope->dcrRefinements[def2] = resultTy;
|
||||
}
|
||||
|
||||
for (auto& c : constraints)
|
||||
|
@ -446,15 +468,15 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
|
|||
|
||||
if (i < local->vars.size)
|
||||
{
|
||||
std::vector<TypeId> packTypes = flatten(*arena, singletonTypes, exprPack, varTypes.size() - i);
|
||||
TypePack packTypes = extendTypePack(*arena, singletonTypes, exprPack, varTypes.size() - i);
|
||||
|
||||
// fill out missing values in varTypes with values from exprPack
|
||||
for (size_t j = i; j < varTypes.size(); ++j)
|
||||
{
|
||||
if (!varTypes[j])
|
||||
{
|
||||
if (j - i < packTypes.size())
|
||||
varTypes[j] = packTypes[j - i];
|
||||
if (j - i < packTypes.head.size())
|
||||
varTypes[j] = packTypes.head[j - i];
|
||||
else
|
||||
varTypes[j] = freshType(scope);
|
||||
}
|
||||
|
@ -591,9 +613,9 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction*
|
|||
FunctionSignature sig = checkFunctionSignature(scope, function->func);
|
||||
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location};
|
||||
|
||||
auto start = checkpoint(this);
|
||||
Checkpoint start = checkpoint(this);
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
auto end = checkpoint(this);
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
||||
std::unique_ptr<Constraint> c =
|
||||
|
@ -611,7 +633,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
|
|||
// Name could be AstStatLocal, AstStatGlobal, AstStatIndexName.
|
||||
// With or without self
|
||||
|
||||
TypeId functionType = nullptr;
|
||||
TypeId generalizedType = arena->addType(BlockedTypeVar{});
|
||||
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func);
|
||||
|
||||
|
@ -620,62 +642,59 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
|
|||
std::optional<TypeId> existingFunctionTy = scope->lookup(localName->local);
|
||||
if (existingFunctionTy)
|
||||
{
|
||||
// Duplicate definition
|
||||
functionType = *existingFunctionTy;
|
||||
addConstraint(scope, function->name->location, SubtypeConstraint{generalizedType, *existingFunctionTy});
|
||||
|
||||
Symbol sym{localName->local};
|
||||
std::optional<DefId> def = dfg->getDef(sym);
|
||||
LUAU_ASSERT(def);
|
||||
scope->bindings[sym].typeId = generalizedType;
|
||||
scope->dcrRefinements[*def] = generalizedType;
|
||||
}
|
||||
else
|
||||
{
|
||||
functionType = arena->addType(BlockedTypeVar{});
|
||||
scope->bindings[localName->local] = Binding{functionType, localName->location};
|
||||
}
|
||||
scope->bindings[localName->local] = Binding{generalizedType, localName->location};
|
||||
|
||||
sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location};
|
||||
}
|
||||
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
|
||||
{
|
||||
std::optional<TypeId> existingFunctionTy = scope->lookup(globalName->name);
|
||||
if (existingFunctionTy)
|
||||
{
|
||||
// Duplicate definition
|
||||
functionType = *existingFunctionTy;
|
||||
}
|
||||
else
|
||||
{
|
||||
functionType = arena->addType(BlockedTypeVar{});
|
||||
rootScope->bindings[globalName->name] = Binding{functionType, globalName->location};
|
||||
}
|
||||
if (!existingFunctionTy)
|
||||
ice->ice("prepopulateGlobalScope did not populate a global name", globalName->location);
|
||||
|
||||
generalizedType = *existingFunctionTy;
|
||||
|
||||
sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
|
||||
}
|
||||
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
||||
{
|
||||
TypeId containingTableType = check(scope, indexName->expr).ty;
|
||||
|
||||
functionType = arena->addType(BlockedTypeVar{});
|
||||
|
||||
// TODO look into stack utilization. This is probably ok because it scales with AST depth.
|
||||
TypeId prospectiveTableType = arena->addType(TableTypeVar{TableState::Unsealed, TypeLevel{}, scope.get()});
|
||||
|
||||
NotNull<TableTypeVar> prospectiveTable{getMutable<TableTypeVar>(prospectiveTableType)};
|
||||
|
||||
Property& prop = prospectiveTable->props[indexName->index.value];
|
||||
prop.type = functionType;
|
||||
prop.type = generalizedType;
|
||||
prop.location = function->name->location;
|
||||
|
||||
addConstraint(scope, indexName->location, SubtypeConstraint{containingTableType, prospectiveTableType});
|
||||
}
|
||||
else if (AstExprError* err = function->name->as<AstExprError>())
|
||||
{
|
||||
functionType = singletonTypes->errorRecoveryType();
|
||||
generalizedType = singletonTypes->errorRecoveryType();
|
||||
}
|
||||
|
||||
LUAU_ASSERT(functionType != nullptr);
|
||||
if (generalizedType == nullptr)
|
||||
ice->ice("generalizedType == nullptr", function->location);
|
||||
|
||||
auto start = checkpoint(this);
|
||||
Checkpoint start = checkpoint(this);
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
auto end = checkpoint(this);
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
||||
std::unique_ptr<Constraint> c =
|
||||
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
|
||||
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature});
|
||||
|
||||
forEachConstraint(start, end, this, [&c](const ConstraintPtr& constraint) {
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
|
@ -708,7 +727,9 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block)
|
|||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
|
||||
{
|
||||
TypePackId varPackId = checkLValues(scope, assign->vars);
|
||||
TypePackId valuePack = checkPack(scope, assign->values).tp;
|
||||
|
||||
TypePack expectedTypes = extendTypePack(*arena, singletonTypes, varPackId, assign->values.size);
|
||||
TypePackId valuePack = checkPack(scope, assign->values, expectedTypes.head).tp;
|
||||
|
||||
addConstraint(scope, assign->location, PackSubtypeConstraint{valuePack, varPackId});
|
||||
}
|
||||
|
@ -729,8 +750,6 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign*
|
|||
|
||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement)
|
||||
{
|
||||
// TODO: Optimization opportunity, the interior scope of the condition could be
|
||||
// reused for the then body, so we don't need to refine twice.
|
||||
ScopePtr condScope = childScope(ifStatement->condition, scope);
|
||||
auto [_, connective] = check(condScope, ifStatement->condition, std::nullopt);
|
||||
|
||||
|
@ -986,7 +1005,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr*
|
|||
InferencePack result;
|
||||
|
||||
if (AstExprCall* call = expr->as<AstExprCall>())
|
||||
result = {checkPack(scope, call, expectedTypes)};
|
||||
result = checkPack(scope, call, expectedTypes);
|
||||
else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>())
|
||||
{
|
||||
if (scope->varargPack)
|
||||
|
@ -1010,38 +1029,101 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr*
|
|||
|
||||
InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCall* call, const std::vector<TypeId>& expectedTypes)
|
||||
{
|
||||
TypeId fnType = check(scope, call->func).ty;
|
||||
auto startCheckpoint = checkpoint(this);
|
||||
|
||||
std::vector<TypeId> args;
|
||||
|
||||
for (AstExpr* arg : call->args)
|
||||
{
|
||||
args.push_back(check(scope, arg).ty);
|
||||
}
|
||||
|
||||
std::vector<AstExpr*> exprArgs;
|
||||
if (call->self)
|
||||
{
|
||||
AstExprIndexName* indexExpr = call->func->as<AstExprIndexName>();
|
||||
if (!indexExpr)
|
||||
ice->ice("method call expression has no 'self'");
|
||||
|
||||
// The call to `check` we already did on `call->func` should have already produced a type for
|
||||
// `indexExpr->expr`, so we can get it from `astTypes` to avoid exponential blow-up.
|
||||
TypeId selfType = astTypes[indexExpr->expr];
|
||||
|
||||
// If we don't have a type for self, it means we had a code too complex error already.
|
||||
if (selfType == nullptr)
|
||||
selfType = singletonTypes->errorRecoveryType();
|
||||
|
||||
args.insert(args.begin(), selfType);
|
||||
exprArgs.push_back(indexExpr->expr);
|
||||
}
|
||||
exprArgs.insert(exprArgs.end(), call->args.begin(), call->args.end());
|
||||
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
TypeId fnType = check(scope, call->func).ty;
|
||||
Checkpoint fnEndCheckpoint = checkpoint(this);
|
||||
|
||||
TypePackId expectedArgPack = arena->freshTypePack(scope.get());
|
||||
TypePackId expectedRetPack = arena->freshTypePack(scope.get());
|
||||
TypeId expectedFunctionType = arena->addType(FunctionTypeVar{expectedArgPack, expectedRetPack});
|
||||
|
||||
TypeId instantiatedFnType = arena->addType(BlockedTypeVar{});
|
||||
addConstraint(scope, call->location, InstantiationConstraint{instantiatedFnType, fnType});
|
||||
|
||||
NotNull<Constraint> extractArgsConstraint = addConstraint(scope, call->location, SubtypeConstraint{instantiatedFnType, expectedFunctionType});
|
||||
|
||||
// Fully solve fnType, then extract its argument list as expectedArgPack.
|
||||
forEachConstraint(startCheckpoint, fnEndCheckpoint, this, [extractArgsConstraint](const ConstraintPtr& constraint) {
|
||||
extractArgsConstraint->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
const AstExpr* lastArg = exprArgs.size() ? exprArgs[exprArgs.size() - 1] : nullptr;
|
||||
const bool needTail = lastArg && (lastArg->is<AstExprCall>() || lastArg->is<AstExprVarargs>());
|
||||
|
||||
TypePack expectedArgs;
|
||||
|
||||
if (!needTail)
|
||||
expectedArgs = extendTypePack(*arena, singletonTypes, expectedArgPack, exprArgs.size());
|
||||
else
|
||||
expectedArgs = extendTypePack(*arena, singletonTypes, expectedArgPack, exprArgs.size() - 1);
|
||||
|
||||
std::vector<ConnectiveId> connectives;
|
||||
if (auto ftv = get<FunctionTypeVar>(follow(fnType)); ftv && ftv->dcrMagicRefinement)
|
||||
{
|
||||
MagicRefinementContext ctx{globalScope, dfg, NotNull{&connectiveArena}, call};
|
||||
connectives = ftv->dcrMagicRefinement(ctx);
|
||||
}
|
||||
|
||||
|
||||
std::vector<TypeId> args;
|
||||
std::optional<TypePackId> argTail;
|
||||
|
||||
Checkpoint argCheckpoint = checkpoint(this);
|
||||
|
||||
for (size_t i = 0; i < exprArgs.size(); ++i)
|
||||
{
|
||||
AstExpr* arg = exprArgs[i];
|
||||
std::optional<TypeId> expectedType;
|
||||
if (i < expectedArgs.head.size())
|
||||
expectedType = expectedArgs.head[i];
|
||||
|
||||
if (i == 0 && call->self)
|
||||
{
|
||||
// The self type has already been computed as a side effect of
|
||||
// computing fnType. If computing that did not cause us to exceed a
|
||||
// recursion limit, we can fetch it from astTypes rather than
|
||||
// recomputing it.
|
||||
TypeId* selfTy = astTypes.find(exprArgs[0]);
|
||||
if (selfTy)
|
||||
args.push_back(*selfTy);
|
||||
else
|
||||
args.push_back(arena->freshType(scope.get()));
|
||||
}
|
||||
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
||||
args.push_back(check(scope, arg, expectedType).ty);
|
||||
else
|
||||
argTail = checkPack(scope, arg, {}).tp; // FIXME? not sure about expectedTypes here
|
||||
}
|
||||
|
||||
Checkpoint argEndCheckpoint = checkpoint(this);
|
||||
|
||||
// Do not solve argument constraints until after we have extracted the
|
||||
// expected types from the callable.
|
||||
forEachConstraint(argCheckpoint, argEndCheckpoint, this, [extractArgsConstraint](const ConstraintPtr& constraint) {
|
||||
constraint->dependencies.push_back(extractArgsConstraint);
|
||||
});
|
||||
|
||||
if (matchSetmetatable(*call))
|
||||
{
|
||||
LUAU_ASSERT(args.size() == 2);
|
||||
TypeId target = args[0];
|
||||
TypeId mt = args[1];
|
||||
TypePack argTailPack;
|
||||
if (argTail && args.size() < 2)
|
||||
argTailPack = extendTypePack(*arena, singletonTypes, *argTail, 2 - args.size());
|
||||
|
||||
LUAU_ASSERT(args.size() + argTailPack.head.size() == 2);
|
||||
|
||||
TypeId target = args.size() > 0 ? args[0] : argTailPack.head[0];
|
||||
TypeId mt = args.size() > 1 ? args[1] : argTailPack.head[args.size() == 0 ? 1 : 0];
|
||||
|
||||
AstExpr* targetExpr = call->args.data[0];
|
||||
|
||||
|
@ -1051,18 +1133,16 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
|||
if (AstExprLocal* targetLocal = targetExpr->as<AstExprLocal>())
|
||||
scope->bindings[targetLocal->local].typeId = resultTy;
|
||||
|
||||
return InferencePack{arena->addTypePack({resultTy})};
|
||||
return InferencePack{arena->addTypePack({resultTy}), std::move(connectives)};
|
||||
}
|
||||
else
|
||||
{
|
||||
auto endCheckpoint = checkpoint(this);
|
||||
|
||||
astOriginalCallTypes[call->func] = fnType;
|
||||
|
||||
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
|
||||
// TODO: How do expectedTypes play into this? Do they?
|
||||
TypePackId rets = arena->addTypePack(BlockedTypePack{});
|
||||
TypePackId argPack = arena->addTypePack(TypePack{args, {}});
|
||||
TypePackId argPack = arena->addTypePack(TypePack{args, argTail});
|
||||
FunctionTypeVar ftv(TypeLevel{}, scope.get(), argPack, rets);
|
||||
TypeId inferredFnType = arena->addType(ftv);
|
||||
|
||||
|
@ -1071,19 +1151,10 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
|||
NotNull<const Constraint> ic(unqueuedConstraints.back().get());
|
||||
|
||||
unqueuedConstraints.push_back(
|
||||
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, SubtypeConstraint{inferredFnType, instantiatedType}));
|
||||
std::make_unique<Constraint>(NotNull{scope.get()}, call->func->location, SubtypeConstraint{instantiatedType, inferredFnType}));
|
||||
NotNull<Constraint> sc(unqueuedConstraints.back().get());
|
||||
|
||||
// We force constraints produced by checking function arguments to wait
|
||||
// until after we have resolved the constraint on the function itself.
|
||||
// This ensures, for instance, that we start inferring the contents of
|
||||
// lambdas under the assumption that their arguments and return types
|
||||
// will be compatible with the enclosing function call.
|
||||
forEachConstraint(startCheckpoint, endCheckpoint, this, [sc](const ConstraintPtr& constraint) {
|
||||
constraint->dependencies.push_back(sc);
|
||||
});
|
||||
|
||||
addConstraint(scope, call->func->location,
|
||||
NotNull<Constraint> fcc = addConstraint(scope, call->func->location,
|
||||
FunctionCallConstraint{
|
||||
{ic, sc},
|
||||
fnType,
|
||||
|
@ -1092,7 +1163,16 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa
|
|||
call,
|
||||
});
|
||||
|
||||
return InferencePack{rets};
|
||||
// We force constraints produced by checking function arguments to wait
|
||||
// until after we have resolved the constraint on the function itself.
|
||||
// This ensures, for instance, that we start inferring the contents of
|
||||
// lambdas under the assumption that their arguments and return types
|
||||
// will be compatible with the enclosing function call.
|
||||
forEachConstraint(fnEndCheckpoint, argEndCheckpoint, this, [fcc](const ConstraintPtr& constraint) {
|
||||
fcc->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
return InferencePack{rets, std::move(connectives)};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1133,9 +1213,19 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, st
|
|||
}
|
||||
else if (auto a = expr->as<AstExprFunction>())
|
||||
{
|
||||
FunctionSignature sig = checkFunctionSignature(scope, a);
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, a, expectedType);
|
||||
checkFunctionBody(sig.bodyScope, a);
|
||||
return Inference{sig.signature};
|
||||
Checkpoint endCheckpoint = checkpoint(this);
|
||||
|
||||
TypeId generalizedTy = arena->addType(BlockedTypeVar{});
|
||||
NotNull<Constraint> gc = addConstraint(scope, expr->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
||||
|
||||
forEachConstraint(startCheckpoint, endCheckpoint, this, [gc](const ConstraintPtr& constraint) {
|
||||
gc->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
return Inference{generalizedTy};
|
||||
}
|
||||
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||
result = check(scope, indexName);
|
||||
|
@ -1253,10 +1343,83 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprGlobal* gl
|
|||
return Inference{singletonTypes->errorRecoveryType()};
|
||||
}
|
||||
|
||||
static std::optional<TypeId> lookupProp(TypeId ty, const std::string& propName, NotNull<TypeArena> arena)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (auto ctv = get<ClassTypeVar>(ty))
|
||||
{
|
||||
if (auto prop = lookupClassProp(ctv, propName))
|
||||
return prop->type;
|
||||
}
|
||||
else if (auto ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
if (auto it = ttv->props.find(propName); it != ttv->props.end())
|
||||
return it->second.type;
|
||||
}
|
||||
else if (auto utv = get<IntersectionTypeVar>(ty))
|
||||
{
|
||||
std::vector<TypeId> types;
|
||||
|
||||
for (TypeId ty : utv)
|
||||
{
|
||||
if (auto prop = lookupProp(ty, propName, arena))
|
||||
{
|
||||
if (std::find(begin(types), end(types), *prop) == end(types))
|
||||
types.push_back(*prop);
|
||||
}
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (types.size() == 1)
|
||||
return types[0];
|
||||
else
|
||||
return arena->addType(IntersectionTypeVar{std::move(types)});
|
||||
}
|
||||
else if (auto utv = get<UnionTypeVar>(ty))
|
||||
{
|
||||
std::vector<TypeId> types;
|
||||
|
||||
for (TypeId ty : utv)
|
||||
{
|
||||
if (auto prop = lookupProp(ty, propName, arena))
|
||||
{
|
||||
if (std::find(begin(types), end(types), *prop) == end(types))
|
||||
types.push_back(*prop);
|
||||
}
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (types.size() == 1)
|
||||
return types[0];
|
||||
else
|
||||
return arena->addType(UnionTypeVar{std::move(types)});
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||
{
|
||||
TypeId obj = check(scope, indexName->expr).ty;
|
||||
TypeId result = freshType(scope);
|
||||
|
||||
// HACK: We need to return the actual type for type refinements so that it can invoke the dcrMagicRefinement function.
|
||||
TypeId result;
|
||||
if (auto prop = lookupProp(obj, indexName->index.value, arena))
|
||||
result = *prop;
|
||||
else
|
||||
result = freshType(scope);
|
||||
|
||||
std::optional<DefId> def = dfg->getDef(indexName);
|
||||
if (def)
|
||||
{
|
||||
if (auto ty = scope->lookup(*def))
|
||||
return Inference{*ty, connectiveArena.proposition(*def, singletonTypes->truthyType)};
|
||||
else
|
||||
scope->dcrRefinements[*def] = result;
|
||||
}
|
||||
|
||||
TableTypeVar::Props props{{indexName->index.value, Property{result}}};
|
||||
const std::optional<TableIndexer> indexer;
|
||||
|
@ -1266,7 +1429,10 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName*
|
|||
|
||||
addConstraint(scope, indexName->expr->location, SubtypeConstraint{obj, expectedTableType});
|
||||
|
||||
return Inference{result};
|
||||
if (def)
|
||||
return Inference{result, connectiveArena.proposition(*def, singletonTypes->truthyType)};
|
||||
else
|
||||
return Inference{result};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
|
||||
|
@ -1555,8 +1721,16 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp
|
|||
{
|
||||
if (auto stringKey = item.key->as<AstExprConstantString>())
|
||||
{
|
||||
expectedValueType = arena->addType(BlockedTypeVar{});
|
||||
addConstraint(scope, item.value->location, HasPropConstraint{*expectedValueType, *expectedType, stringKey->value.data});
|
||||
ErrorVec errorVec;
|
||||
std::optional<TypeId> propTy =
|
||||
findTablePropertyRespectingMeta(singletonTypes, errorVec, follow(*expectedType), stringKey->value.data, item.value->location);
|
||||
if (propTy)
|
||||
expectedValueType = propTy;
|
||||
else
|
||||
{
|
||||
expectedValueType = arena->addType(BlockedTypeVar{});
|
||||
addConstraint(scope, item.value->location, HasPropConstraint{*expectedValueType, *expectedType, stringKey->value.data});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1590,7 +1764,8 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp
|
|||
return Inference{ty};
|
||||
}
|
||||
|
||||
ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn)
|
||||
ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(
|
||||
const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType)
|
||||
{
|
||||
ScopePtr signatureScope = nullptr;
|
||||
ScopePtr bodyScope = nullptr;
|
||||
|
@ -1599,22 +1774,22 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||
std::vector<TypeId> genericTypes;
|
||||
std::vector<TypePackId> genericTypePacks;
|
||||
|
||||
if (expectedType)
|
||||
expectedType = follow(*expectedType);
|
||||
|
||||
bool hasGenerics = fn->generics.size > 0 || fn->genericPacks.size > 0;
|
||||
|
||||
// If we don't have any generics, we can save some memory and compute by not
|
||||
// creating the signatureScope, which is only used to scope the declared
|
||||
// generics properly.
|
||||
signatureScope = childScope(fn, parent);
|
||||
|
||||
// We need to assign returnType before creating bodyScope so that the
|
||||
// return type gets propogated to bodyScope.
|
||||
returnType = freshTypePack(signatureScope);
|
||||
signatureScope->returnType = returnType;
|
||||
|
||||
bodyScope = childScope(fn->body, signatureScope);
|
||||
|
||||
if (hasGenerics)
|
||||
{
|
||||
signatureScope = childScope(fn, parent);
|
||||
|
||||
// We need to assign returnType before creating bodyScope so that the
|
||||
// return type gets propogated to bodyScope.
|
||||
returnType = freshTypePack(signatureScope);
|
||||
signatureScope->returnType = returnType;
|
||||
|
||||
bodyScope = childScope(fn->body, signatureScope);
|
||||
|
||||
std::vector<std::pair<Name, GenericTypeDefinition>> genericDefinitions = createGenerics(signatureScope, fn->generics);
|
||||
std::vector<std::pair<Name, GenericTypePackDefinition>> genericPackDefinitions = createGenericPacks(signatureScope, fn->genericPacks);
|
||||
|
||||
|
@ -1631,18 +1806,48 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||
genericTypePacks.push_back(g.tp);
|
||||
signatureScope->privateTypePackBindings[name] = g.tp;
|
||||
}
|
||||
|
||||
expectedType.reset();
|
||||
}
|
||||
else
|
||||
|
||||
std::vector<TypeId> argTypes;
|
||||
TypePack expectedArgPack;
|
||||
|
||||
const FunctionTypeVar* expectedFunction = expectedType ? get<FunctionTypeVar>(*expectedType) : nullptr;
|
||||
|
||||
if (expectedFunction)
|
||||
{
|
||||
bodyScope = childScope(fn, parent);
|
||||
expectedArgPack = extendTypePack(*arena, singletonTypes, expectedFunction->argTypes, fn->args.size);
|
||||
|
||||
returnType = freshTypePack(bodyScope);
|
||||
bodyScope->returnType = returnType;
|
||||
genericTypes = expectedFunction->generics;
|
||||
genericTypePacks = expectedFunction->genericPacks;
|
||||
}
|
||||
|
||||
// To eliminate the need to branch on hasGenerics below, we say that the
|
||||
// signature scope is the body scope when there is no real signature
|
||||
// scope.
|
||||
signatureScope = bodyScope;
|
||||
for (size_t i = 0; i < fn->args.size; ++i)
|
||||
{
|
||||
AstLocal* local = fn->args.data[i];
|
||||
|
||||
TypeId t = freshType(signatureScope);
|
||||
argTypes.push_back(t);
|
||||
signatureScope->bindings[local] = Binding{t, local->location};
|
||||
|
||||
TypeId annotationTy = t;
|
||||
|
||||
if (local->annotation)
|
||||
{
|
||||
annotationTy = resolveType(signatureScope, local->annotation, /* topLevel */ true);
|
||||
addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, annotationTy});
|
||||
}
|
||||
else if (i < expectedArgPack.head.size())
|
||||
{
|
||||
addConstraint(signatureScope, local->location, SubtypeConstraint{t, expectedArgPack.head[i]});
|
||||
}
|
||||
|
||||
// HACK: This is the one case where the type of the definition will diverge from the type of the binding.
|
||||
// We need to do this because there are cases where type refinements needs to have the information available
|
||||
// at constraint generation time.
|
||||
if (auto def = dfg->getDef(local))
|
||||
signatureScope->dcrRefinements[*def] = annotationTy;
|
||||
}
|
||||
|
||||
TypePackId varargPack = nullptr;
|
||||
|
@ -1654,22 +1859,28 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||
TypePackId annotationType = resolveTypePack(signatureScope, fn->varargAnnotation);
|
||||
varargPack = annotationType;
|
||||
}
|
||||
else if (expectedArgPack.tail && get<VariadicTypePack>(*expectedArgPack.tail))
|
||||
varargPack = *expectedArgPack.tail;
|
||||
else
|
||||
{
|
||||
varargPack = arena->freshTypePack(signatureScope.get());
|
||||
}
|
||||
varargPack = singletonTypes->anyTypePack;
|
||||
|
||||
signatureScope->varargPack = varargPack;
|
||||
bodyScope->varargPack = varargPack;
|
||||
}
|
||||
else
|
||||
{
|
||||
varargPack = arena->addTypePack(VariadicTypePack{singletonTypes->anyType, /*hidden*/ true});
|
||||
// We do not add to signatureScope->varargPack because ... is not valid
|
||||
// in functions without an explicit ellipsis.
|
||||
|
||||
signatureScope->varargPack = std::nullopt;
|
||||
bodyScope->varargPack = std::nullopt;
|
||||
}
|
||||
|
||||
LUAU_ASSERT(nullptr != varargPack);
|
||||
|
||||
// If there is both an annotation and an expected type, the annotation wins.
|
||||
// Type checking will sort out any discrepancies later.
|
||||
if (fn->returnAnnotation)
|
||||
{
|
||||
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation);
|
||||
|
@ -1680,26 +1891,11 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||
LUAU_ASSERT(get<FreeTypePack>(returnType));
|
||||
asMutable(returnType)->ty.emplace<BoundTypePack>(annotatedRetType);
|
||||
}
|
||||
|
||||
std::vector<TypeId> argTypes;
|
||||
|
||||
for (AstLocal* local : fn->args)
|
||||
else if (expectedFunction)
|
||||
{
|
||||
TypeId t = freshType(signatureScope);
|
||||
argTypes.push_back(t);
|
||||
signatureScope->bindings[local] = Binding{t, local->location};
|
||||
|
||||
if (auto def = dfg->getDef(local))
|
||||
signatureScope->dcrRefinements[*def] = t;
|
||||
|
||||
if (local->annotation)
|
||||
{
|
||||
TypeId argAnnotation = resolveType(signatureScope, local->annotation, /* topLevel */ true);
|
||||
addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, argAnnotation});
|
||||
}
|
||||
asMutable(returnType)->ty.emplace<BoundTypePack>(expectedFunction->retTypes);
|
||||
}
|
||||
|
||||
// TODO: Vararg annotation.
|
||||
// TODO: Preserve argument names in the function's type.
|
||||
|
||||
FunctionTypeVar actualFunction{TypeLevel{}, parent.get(), arena->addTypePack(argTypes, varargPack), returnType};
|
||||
|
@ -1711,11 +1907,14 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||
LUAU_ASSERT(actualFunctionType);
|
||||
astTypes[fn] = actualFunctionType;
|
||||
|
||||
if (expectedType && get<FreeTypeVar>(*expectedType))
|
||||
{
|
||||
asMutable(*expectedType)->ty.emplace<BoundTypeVar>(actualFunctionType);
|
||||
}
|
||||
|
||||
return {
|
||||
/* signature */ actualFunctionType,
|
||||
// Undo the workaround we made above: if there's no signature scope,
|
||||
// don't report it.
|
||||
/* signatureScope */ hasGenerics ? signatureScope : nullptr,
|
||||
/* signatureScope */ signatureScope,
|
||||
/* bodyScope */ bodyScope,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1233,9 +1233,22 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
|||
if (isBlocked(subjectType) || get<PendingExpansionTypeVar>(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
|
||||
if (get<FreeTypeVar>(subjectType))
|
||||
{
|
||||
TableTypeVar& ttv = asMutable(subjectType)->ty.emplace<TableTypeVar>(TableState::Free, TypeLevel{}, constraint->scope);
|
||||
ttv.props[c.prop] = Property{c.resultType};
|
||||
asMutable(c.resultType)->ty.emplace<FreeTypeVar>(constraint->scope);
|
||||
unblock(c.resultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<TypeId> resultType = lookupTableProp(subjectType, c.prop);
|
||||
if (!resultType)
|
||||
return false;
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->errorRecoveryType());
|
||||
unblock(c.resultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isBlocked(*resultType))
|
||||
{
|
||||
|
@ -1418,8 +1431,10 @@ bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNul
|
|||
TypeId followed = follow(c.discriminantType);
|
||||
|
||||
// `nil` is a singleton type too! There's only one value of type `nil`.
|
||||
if (get<SingletonTypeVar>(followed) || isNil(followed))
|
||||
if (c.negated && (get<SingletonTypeVar>(followed) || isNil(followed)))
|
||||
*asMutable(c.resultType) = NegationTypeVar{c.discriminantType};
|
||||
else if (!c.negated && get<SingletonTypeVar>(followed))
|
||||
*asMutable(c.resultType) = BoundTypeVar{c.discriminantType};
|
||||
else
|
||||
*asMutable(c.resultType) = BoundTypeVar{singletonTypes->unknownType};
|
||||
|
||||
|
@ -1509,17 +1524,17 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
|||
TypePackId expectedIterArgs = arena->addTypePack({iteratorTy});
|
||||
unify(iterFtv->argTypes, expectedIterArgs, constraint->scope);
|
||||
|
||||
std::vector<TypeId> iterRets = flatten(*arena, singletonTypes, iterFtv->retTypes, 2);
|
||||
TypePack iterRets = extendTypePack(*arena, singletonTypes, iterFtv->retTypes, 2);
|
||||
|
||||
if (iterRets.size() < 1)
|
||||
if (iterRets.head.size() < 1)
|
||||
{
|
||||
// We've done what we can; this will get reported as an
|
||||
// error by the type checker.
|
||||
return true;
|
||||
}
|
||||
|
||||
TypeId nextFn = iterRets[0];
|
||||
TypeId table = iterRets.size() == 2 ? iterRets[1] : arena->freshType(constraint->scope);
|
||||
TypeId nextFn = iterRets.head[0];
|
||||
TypeId table = iterRets.head.size() == 2 ? iterRets.head[1] : arena->freshType(constraint->scope);
|
||||
|
||||
if (std::optional<TypeId> instantiatedNextFn = instantiation.substitute(nextFn))
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/DataFlowGraphBuilder.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
|
||||
#include "Luau/Error.h"
|
||||
|
||||
|
@ -11,6 +11,9 @@ namespace Luau
|
|||
|
||||
std::optional<DefId> DataFlowGraph::getDef(const AstExpr* expr) const
|
||||
{
|
||||
// We need to skip through AstExprGroup because DFG doesn't try its best to transitively
|
||||
while (auto group = expr->as<AstExprGroup>())
|
||||
expr = group->expr;
|
||||
if (auto def = astDefs.find(expr))
|
||||
return NotNull{*def};
|
||||
return std::nullopt;
|
||||
|
@ -52,16 +55,25 @@ std::optional<DefId> DataFlowGraphBuilder::use(DfgScope* scope, Symbol symbol, A
|
|||
{
|
||||
for (DfgScope* current = scope; current; current = current->parent)
|
||||
{
|
||||
if (auto loc = current->bindings.find(symbol))
|
||||
if (auto def = current->bindings.find(symbol))
|
||||
{
|
||||
graph.astDefs[e] = *loc;
|
||||
return NotNull{*loc};
|
||||
graph.astDefs[e] = *def;
|
||||
return NotNull{*def};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
DefId DataFlowGraphBuilder::use(DefId def, AstExprIndexName* e)
|
||||
{
|
||||
auto& propertyDef = props[def][e->index.value];
|
||||
if (!propertyDef)
|
||||
propertyDef = arena->freshCell(def, e->index.value);
|
||||
graph.astDefs[e] = propertyDef;
|
||||
return NotNull{propertyDef};
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatBlock* b)
|
||||
{
|
||||
DfgScope* child = childScope(scope);
|
||||
|
@ -180,7 +192,7 @@ void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocal* l)
|
|||
|
||||
for (AstLocal* local : l->vars)
|
||||
{
|
||||
DefId def = arena->freshDef();
|
||||
DefId def = arena->freshCell();
|
||||
graph.localDefs[local] = def;
|
||||
scope->bindings[local] = def;
|
||||
}
|
||||
|
@ -189,7 +201,7 @@ void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocal* l)
|
|||
void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFor* f)
|
||||
{
|
||||
DfgScope* forScope = childScope(scope); // TODO: loop scope.
|
||||
DefId def = arena->freshDef();
|
||||
DefId def = arena->freshCell();
|
||||
graph.localDefs[f->var] = def;
|
||||
scope->bindings[f->var] = def;
|
||||
|
||||
|
@ -203,7 +215,7 @@ void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatForIn* f)
|
|||
|
||||
for (AstLocal* local : f->vars)
|
||||
{
|
||||
DefId def = arena->freshDef();
|
||||
DefId def = arena->freshCell();
|
||||
graph.localDefs[local] = def;
|
||||
forScope->bindings[local] = def;
|
||||
}
|
||||
|
@ -245,7 +257,7 @@ void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatAssign* a)
|
|||
// TODO global?
|
||||
if (auto exprLocal = root->as<AstExprLocal>())
|
||||
{
|
||||
DefId def = arena->freshDef();
|
||||
DefId def = arena->freshCell();
|
||||
graph.astDefs[exprLocal] = def;
|
||||
|
||||
// Update the def in the scope that introduced the local. Not
|
||||
|
@ -277,7 +289,7 @@ void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFunction* f)
|
|||
|
||||
void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocalFunction* l)
|
||||
{
|
||||
DefId def = arena->freshDef();
|
||||
DefId def = arena->freshCell();
|
||||
graph.localDefs[l->name] = def;
|
||||
scope->bindings[l->name] = def;
|
||||
|
||||
|
@ -354,8 +366,7 @@ ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprInde
|
|||
if (!def)
|
||||
return {};
|
||||
|
||||
// TODO: properties for the above def.
|
||||
return {};
|
||||
return {use(*def, i)};
|
||||
}
|
||||
|
||||
ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprIndexExpr* i)
|
||||
|
@ -375,14 +386,14 @@ ExpressionFlowGraph DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprFunc
|
|||
{
|
||||
if (AstLocal* self = f->self)
|
||||
{
|
||||
DefId def = arena->freshDef();
|
||||
DefId def = arena->freshCell();
|
||||
graph.localDefs[self] = def;
|
||||
scope->bindings[self] = def;
|
||||
}
|
||||
|
||||
for (AstLocal* param : f->args)
|
||||
{
|
||||
DefId def = arena->freshDef();
|
||||
DefId def = arena->freshCell();
|
||||
graph.localDefs[param] = def;
|
||||
scope->bindings[param] = def;
|
||||
}
|
|
@ -4,9 +4,14 @@
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
DefId DefArena::freshDef()
|
||||
DefId DefArena::freshCell()
|
||||
{
|
||||
return NotNull{allocator.allocate<Def>(Undefined{})};
|
||||
return NotNull{allocator.allocate<Def>(Def{Cell{std::nullopt}})};
|
||||
}
|
||||
|
||||
DefId DefArena::freshCell(DefId parent, const std::string& prop)
|
||||
{
|
||||
return NotNull{allocator.allocate<Def>(Def{Cell{FieldMetadata{parent, prop}}})};
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
#include "Luau/Error.h"
|
||||
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauIceExceptionInheritanceChange, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeMismatchInvarianceInError, false)
|
||||
|
||||
static std::string wrongNumberOfArgsString(
|
||||
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
||||
|
@ -89,6 +91,7 @@ struct ErrorConverter
|
|||
if (result.empty())
|
||||
result = "Type '" + givenTypeName + "' could not be converted into '" + wantedTypeName + "'";
|
||||
|
||||
|
||||
if (tm.error)
|
||||
{
|
||||
result += "\ncaused by:\n ";
|
||||
|
@ -102,6 +105,10 @@ struct ErrorConverter
|
|||
{
|
||||
result += "; " + tm.reason;
|
||||
}
|
||||
else if (FFlag::LuauTypeMismatchInvarianceInError && tm.context == TypeMismatch::InvariantContext)
|
||||
{
|
||||
result += " in an invariant context";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -467,6 +474,11 @@ struct ErrorConverter
|
|||
{
|
||||
return "Type pack '" + toString(e.givenTp) + "' could not be converted into '" + toString(e.wantedTp) + "'";
|
||||
}
|
||||
|
||||
std::string operator()(const DynamicPropertyLookupOnClassesUnsafe& e) const
|
||||
{
|
||||
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
|
@ -514,6 +526,30 @@ TypeMismatch::TypeMismatch(TypeId wantedType, TypeId givenType, std::string reas
|
|||
{
|
||||
}
|
||||
|
||||
TypeMismatch::TypeMismatch(TypeId wantedType, TypeId givenType, TypeMismatch::Context context)
|
||||
: wantedType(wantedType)
|
||||
, givenType(givenType)
|
||||
, context(context)
|
||||
{
|
||||
}
|
||||
|
||||
TypeMismatch::TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, TypeMismatch::Context context)
|
||||
: wantedType(wantedType)
|
||||
, givenType(givenType)
|
||||
, context(context)
|
||||
, reason(reason)
|
||||
{
|
||||
}
|
||||
|
||||
TypeMismatch::TypeMismatch(TypeId wantedType, TypeId givenType, std::string reason, std::optional<TypeError> error, TypeMismatch::Context context)
|
||||
: wantedType(wantedType)
|
||||
, givenType(givenType)
|
||||
, context(context)
|
||||
, reason(reason)
|
||||
, error(error ? std::make_shared<TypeError>(std::move(*error)) : nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
bool TypeMismatch::operator==(const TypeMismatch& rhs) const
|
||||
{
|
||||
if (!!error != !!rhs.error)
|
||||
|
@ -522,7 +558,7 @@ bool TypeMismatch::operator==(const TypeMismatch& rhs) const
|
|||
if (error && !(*error == *rhs.error))
|
||||
return false;
|
||||
|
||||
return *wantedType == *rhs.wantedType && *givenType == *rhs.givenType && reason == rhs.reason;
|
||||
return *wantedType == *rhs.wantedType && *givenType == *rhs.givenType && reason == rhs.reason && context == rhs.context;
|
||||
}
|
||||
|
||||
bool UnknownSymbol::operator==(const UnknownSymbol& rhs) const
|
||||
|
@ -662,7 +698,17 @@ bool FunctionExitsWithoutReturning::operator==(const FunctionExitsWithoutReturni
|
|||
|
||||
int TypeError::code() const
|
||||
{
|
||||
return 1000 + int(data.index());
|
||||
return minCode() + int(data.index());
|
||||
}
|
||||
|
||||
int TypeError::minCode()
|
||||
{
|
||||
return 1000;
|
||||
}
|
||||
|
||||
TypeErrorSummary TypeError::summary() const
|
||||
{
|
||||
return TypeErrorSummary{location, moduleName, code()};
|
||||
}
|
||||
|
||||
bool TypeError::operator==(const TypeError& rhs) const
|
||||
|
@ -730,6 +776,11 @@ bool TypePackMismatch::operator==(const TypePackMismatch& rhs) const
|
|||
return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp;
|
||||
}
|
||||
|
||||
bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const
|
||||
{
|
||||
return ty == rhs.ty;
|
||||
}
|
||||
|
||||
std::string toString(const TypeError& error)
|
||||
{
|
||||
return toString(error, TypeErrorToStringOptions{});
|
||||
|
@ -886,6 +937,8 @@ void copyError(T& e, TypeArena& destArena, CloneState cloneState)
|
|||
e.wantedTp = clone(e.wantedTp);
|
||||
e.givenTp = clone(e.givenTp);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
|
||||
e.ty = clone(e.ty);
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
@ -930,30 +983,4 @@ const char* InternalCompilerError::what() const throw()
|
|||
return this->message.data();
|
||||
}
|
||||
|
||||
// TODO: Inline me when LuauIceExceptionInheritanceChange is deleted.
|
||||
void throwRuntimeError(const std::string& message)
|
||||
{
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
throw InternalCompilerError(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Inline me when LuauIceExceptionInheritanceChange is deleted.
|
||||
void throwRuntimeError(const std::string& message, const std::string& moduleName)
|
||||
{
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
throw InternalCompilerError(message, moduleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "Luau/Config.h"
|
||||
#include "Luau/ConstraintGraphBuilder.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/DataFlowGraphBuilder.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/DcrLogger.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Parser.h"
|
||||
|
@ -31,7 +31,6 @@ LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixMarkDirtyReverseDeps, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPersistTypesAfterGeneratingDocSyms, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -112,57 +111,32 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
|
|||
|
||||
CloneState cloneState;
|
||||
|
||||
if (FFlag::LuauPersistTypesAfterGeneratingDocSyms)
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->getModuleScope()->exportedTypeBindings.size());
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->getModuleScope()->exportedTypeBindings.size());
|
||||
TypeId globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
globalScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
globalScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
globalScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
typesToPersist.push_back(globalTy.type);
|
||||
}
|
||||
|
||||
for (TypeId ty : typesToPersist)
|
||||
{
|
||||
persist(ty);
|
||||
}
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
else
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
globalScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
TypeFun globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
globalScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
persist(globalTy);
|
||||
}
|
||||
typesToPersist.push_back(globalTy.type);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
globalScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
persist(globalTy.type);
|
||||
}
|
||||
for (TypeId ty : typesToPersist)
|
||||
{
|
||||
persist(ty);
|
||||
}
|
||||
|
||||
return LoadDefinitionFileResult{true, parseResult, checkedModule};
|
||||
|
@ -194,57 +168,32 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
|||
|
||||
CloneState cloneState;
|
||||
|
||||
if (FFlag::LuauPersistTypesAfterGeneratingDocSyms)
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->getModuleScope()->exportedTypeBindings.size());
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->getModuleScope()->exportedTypeBindings.size());
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
typesToPersist.push_back(globalTy.type);
|
||||
}
|
||||
|
||||
for (TypeId ty : typesToPersist)
|
||||
{
|
||||
persist(ty);
|
||||
}
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
else
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[typeChecker.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
persist(globalTy);
|
||||
}
|
||||
typesToPersist.push_back(globalTy.type);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
persist(globalTy.type);
|
||||
}
|
||||
for (TypeId ty : typesToPersist)
|
||||
{
|
||||
persist(ty);
|
||||
}
|
||||
|
||||
return LoadDefinitionFileResult{true, parseResult, checkedModule};
|
||||
|
@ -493,13 +442,13 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
{
|
||||
auto it2 = moduleResolverForAutocomplete.modules.find(name);
|
||||
if (it2 == moduleResolverForAutocomplete.modules.end() || it2->second == nullptr)
|
||||
throwRuntimeError("Frontend::modules does not have data for " + name, name);
|
||||
throw InternalCompilerError("Frontend::modules does not have data for " + name, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it2 = moduleResolver.modules.find(name);
|
||||
if (it2 == moduleResolver.modules.end() || it2->second == nullptr)
|
||||
throwRuntimeError("Frontend::modules does not have data for " + name, name);
|
||||
throw InternalCompilerError("Frontend::modules does not have data for " + name, name);
|
||||
}
|
||||
|
||||
return CheckResult{
|
||||
|
@ -606,7 +555,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||
|
||||
if (module == nullptr)
|
||||
throwRuntimeError("Frontend::check produced a nullptr module for " + moduleName, moduleName);
|
||||
throw InternalCompilerError("Frontend::check produced a nullptr module for " + moduleName, moduleName);
|
||||
|
||||
if (!frontendOptions.retainFullTypeGraphs)
|
||||
{
|
||||
|
|
|
@ -190,6 +190,8 @@ static void errorToString(std::ostream& stream, const T& err)
|
|||
stream << "NormalizationTooComplex { }";
|
||||
else if constexpr (std::is_same_v<T, TypePackMismatch>)
|
||||
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
|
||||
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
|
||||
stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }";
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ LUAU_FASTFLAGVARIABLE(LuauNegatedFunctionTypes, false);
|
|||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauOverloadedFunctionSubtypingPerf);
|
||||
LUAU_FASTFLAG(LuauUninhabitedSubAnything)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -240,13 +241,75 @@ NormalizedType::NormalizedType(NotNull<SingletonTypes> singletonTypes)
|
|||
{
|
||||
}
|
||||
|
||||
static bool isInhabited(const NormalizedType& norm)
|
||||
static bool isShallowInhabited(const NormalizedType& norm)
|
||||
{
|
||||
// This test is just a shallow check, for example it returns `true` for `{ p : never }`
|
||||
return !get<NeverTypeVar>(norm.tops) || !get<NeverTypeVar>(norm.booleans) || !norm.classes.empty() || !get<NeverTypeVar>(norm.errors) ||
|
||||
!get<NeverTypeVar>(norm.nils) || !get<NeverTypeVar>(norm.numbers) || !norm.strings.isNever() || !get<NeverTypeVar>(norm.threads) ||
|
||||
!norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
|
||||
}
|
||||
|
||||
bool isInhabited_DEPRECATED(const NormalizedType& norm)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauUninhabitedSubAnything);
|
||||
return isShallowInhabited(norm);
|
||||
}
|
||||
|
||||
bool Normalizer::isInhabited(const NormalizedType* norm, std::unordered_set<TypeId> seen)
|
||||
{
|
||||
if (!get<NeverTypeVar>(norm->tops) || !get<NeverTypeVar>(norm->booleans) || !get<NeverTypeVar>(norm->errors) ||
|
||||
!get<NeverTypeVar>(norm->nils) || !get<NeverTypeVar>(norm->numbers) || !get<NeverTypeVar>(norm->threads) ||
|
||||
!norm->classes.empty() || !norm->strings.isNever() || !norm->functions.isNever())
|
||||
return true;
|
||||
|
||||
for (const auto& [_, intersect] : norm->tyvars)
|
||||
{
|
||||
if (isInhabited(intersect.get(), seen))
|
||||
return true;
|
||||
}
|
||||
|
||||
for (TypeId table : norm->tables)
|
||||
{
|
||||
if (isInhabited(table, seen))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Normalizer::isInhabited(TypeId ty, std::unordered_set<TypeId> seen)
|
||||
{
|
||||
// TODO: use log.follow(ty), CLI-64291
|
||||
ty = follow(ty);
|
||||
|
||||
if (get<NeverTypeVar>(ty))
|
||||
return false;
|
||||
|
||||
if (!get<IntersectionTypeVar>(ty) && !get<UnionTypeVar>(ty) && !get<TableTypeVar>(ty) && !get<MetatableTypeVar>(ty))
|
||||
return true;
|
||||
|
||||
if (seen.count(ty))
|
||||
return true;
|
||||
|
||||
seen.insert(ty);
|
||||
|
||||
if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
||||
{
|
||||
for (const auto& [_, prop] : ttv->props)
|
||||
{
|
||||
if (!isInhabited(prop.type, seen))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const MetatableTypeVar* mtv = get<MetatableTypeVar>(ty))
|
||||
return isInhabited(mtv->table, seen) && isInhabited(mtv->metatable, seen);
|
||||
|
||||
const NormalizedType* norm = normalize(ty);
|
||||
return isInhabited(norm, seen);
|
||||
}
|
||||
|
||||
static int tyvarIndex(TypeId ty)
|
||||
{
|
||||
if (const GenericTypeVar* gtv = get<GenericTypeVar>(ty))
|
||||
|
@ -378,7 +441,7 @@ static bool isNormalizedTyvar(const NormalizedTyvars& tyvars)
|
|||
{
|
||||
if (!isPlainTyvar(tyvar))
|
||||
return false;
|
||||
if (!isInhabited(*intersect))
|
||||
if (!isShallowInhabited(*intersect))
|
||||
return false;
|
||||
for (auto& [other, _] : intersect->tyvars)
|
||||
if (tyvarIndex(other) <= tyvarIndex(tyvar))
|
||||
|
@ -1852,7 +1915,7 @@ bool Normalizer::intersectTyvarsWithTy(NormalizedTyvars& here, TypeId there)
|
|||
NormalizedType& inter = *it->second;
|
||||
if (!intersectNormalWithTy(inter, there))
|
||||
return false;
|
||||
if (isInhabited(inter))
|
||||
if (isShallowInhabited(inter))
|
||||
++it;
|
||||
else
|
||||
it = here.erase(it);
|
||||
|
@ -1914,7 +1977,7 @@ bool Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& th
|
|||
if (!intersectNormals(inter, *found->second, index))
|
||||
return false;
|
||||
}
|
||||
if (isInhabited(inter))
|
||||
if (isShallowInhabited(inter))
|
||||
it++;
|
||||
else
|
||||
it = here.tyvars.erase(it);
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauLvaluelessPath)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLineBreaksDetermineIndents, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false)
|
||||
|
||||
|
@ -272,10 +272,20 @@ struct StringifierState
|
|||
private:
|
||||
void emitIndentation()
|
||||
{
|
||||
if (!opts.indent)
|
||||
return;
|
||||
if (!FFlag::LuauLineBreaksDetermineIndents)
|
||||
{
|
||||
if (!opts.DEPRECATED_indent)
|
||||
return;
|
||||
|
||||
emit(std::string(indentation, ' '));
|
||||
emit(std::string(indentation, ' '));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!opts.useLineBreaks)
|
||||
return;
|
||||
|
||||
emit(std::string(indentation, ' '));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -444,7 +454,7 @@ struct TypeVarStringifier
|
|||
return;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unknown primitive type");
|
||||
throwRuntimeError("Unknown primitive type " + std::to_string(ptv.type));
|
||||
throw InternalCompilerError("Unknown primitive type " + std::to_string(ptv.type));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -461,7 +471,7 @@ struct TypeVarStringifier
|
|||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unknown singleton type");
|
||||
throwRuntimeError("Unknown singleton type");
|
||||
throw InternalCompilerError("Unknown singleton type");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -507,24 +517,13 @@ struct TypeVarStringifier
|
|||
|
||||
bool plural = true;
|
||||
|
||||
if (FFlag::LuauFunctionReturnStringificationFixup)
|
||||
auto retBegin = begin(ftv.retTypes);
|
||||
auto retEnd = end(ftv.retTypes);
|
||||
if (retBegin != retEnd)
|
||||
{
|
||||
auto retBegin = begin(ftv.retTypes);
|
||||
auto retEnd = end(ftv.retTypes);
|
||||
if (retBegin != retEnd)
|
||||
{
|
||||
++retBegin;
|
||||
if (retBegin == retEnd && !retBegin.tail())
|
||||
plural = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto retPack = get<TypePack>(follow(ftv.retTypes)))
|
||||
{
|
||||
if (retPack->head.size() == 1 && !retPack->tail)
|
||||
plural = false;
|
||||
}
|
||||
++retBegin;
|
||||
if (retBegin == retEnd && !retBegin.tail())
|
||||
plural = false;
|
||||
}
|
||||
|
||||
if (plural)
|
||||
|
@ -978,8 +977,6 @@ struct TypePackStringifier
|
|||
|
||||
void operator()(TypePackId tp, const GenericTypePack& pack)
|
||||
{
|
||||
if (FFlag::DebugLuauVerboseTypeNames)
|
||||
state.emit("gen-");
|
||||
if (pack.explicitName)
|
||||
{
|
||||
state.usedNames.insert(pack.name);
|
||||
|
@ -990,6 +987,15 @@ struct TypePackStringifier
|
|||
{
|
||||
state.emit(state.getName(tp));
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauVerboseTypeNames)
|
||||
{
|
||||
state.emit("-");
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
state.emitLevel(pack.scope);
|
||||
else
|
||||
state.emit(pack.level);
|
||||
}
|
||||
state.emit("...");
|
||||
}
|
||||
|
||||
|
@ -1139,7 +1145,7 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
|
|||
else
|
||||
tvs.stringify(ty);
|
||||
|
||||
if (!state.cycleNames.empty())
|
||||
if (!state.cycleNames.empty() || !state.cycleTpNames.empty())
|
||||
{
|
||||
result.cycle = true;
|
||||
state.emit(" where ");
|
||||
|
@ -1169,6 +1175,29 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts)
|
|||
semi = true;
|
||||
}
|
||||
|
||||
std::vector<std::pair<TypePackId, std::string>> sortedCycleTpNames(state.cycleTpNames.begin(), state.cycleTpNames.end());
|
||||
std::sort(sortedCycleTpNames.begin(), sortedCycleTpNames.end(), [](const auto& a, const auto& b) {
|
||||
return a.second < b.second;
|
||||
});
|
||||
|
||||
TypePackStringifier tps{state};
|
||||
|
||||
for (const auto& [cycleTp, name] : sortedCycleTpNames)
|
||||
{
|
||||
if (semi)
|
||||
state.emit(" ; ");
|
||||
|
||||
state.emit(name);
|
||||
state.emit(" = ");
|
||||
Luau::visit(
|
||||
[&tps, cycleTy = cycleTp](auto&& t) {
|
||||
return tps(cycleTy, t);
|
||||
},
|
||||
cycleTp->ty);
|
||||
|
||||
semi = true;
|
||||
}
|
||||
|
||||
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
|
||||
{
|
||||
result.truncated = true;
|
||||
|
@ -1351,22 +1380,30 @@ std::string toStringNamedFunction(const std::string& funcName, const FunctionTyp
|
|||
return result.name;
|
||||
}
|
||||
|
||||
static ToStringOptions& dumpOptions()
|
||||
{
|
||||
static ToStringOptions opts = ([]() {
|
||||
ToStringOptions o;
|
||||
o.exhaustive = true;
|
||||
o.functionTypeArguments = true;
|
||||
o.maxTableLength = 0;
|
||||
o.maxTypeLength = 0;
|
||||
return o;
|
||||
})();
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
std::string dump(TypeId ty)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
opts.functionTypeArguments = true;
|
||||
std::string s = toString(ty, opts);
|
||||
std::string s = toString(ty, dumpOptions());
|
||||
printf("%s\n", s.c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string dump(TypePackId ty)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
opts.functionTypeArguments = true;
|
||||
std::string s = toString(ty, opts);
|
||||
std::string s = toString(ty, dumpOptions());
|
||||
printf("%s\n", s.c_str());
|
||||
return s;
|
||||
}
|
||||
|
@ -1381,10 +1418,7 @@ std::string dump(const ScopePtr& scope, const char* name)
|
|||
}
|
||||
|
||||
TypeId ty = binding->typeId;
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
opts.functionTypeArguments = true;
|
||||
std::string s = toString(ty, opts);
|
||||
std::string s = toString(ty, dumpOptions());
|
||||
printf("%s\n", s.c_str());
|
||||
return s;
|
||||
}
|
||||
|
@ -1403,8 +1437,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||
auto go = [&opts](auto&& c) -> std::string {
|
||||
using T = std::decay_t<decltype(c)>;
|
||||
|
||||
auto tos = [&opts](auto&& a)
|
||||
{
|
||||
auto tos = [&opts](auto&& a) {
|
||||
return toString(a, opts);
|
||||
};
|
||||
|
||||
|
@ -1470,8 +1503,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||
}
|
||||
else if constexpr (std::is_same_v<T, PrimitiveTypeConstraint>)
|
||||
{
|
||||
return tos(c.resultType) + " ~ prim " + tos(c.expectedType) + ", " + tos(c.singletonType) + ", " +
|
||||
tos(c.multitonType);
|
||||
return tos(c.resultType) + " ~ prim " + tos(c.expectedType) + ", " + tos(c.singletonType) + ", " + tos(c.multitonType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, HasPropConstraint>)
|
||||
{
|
||||
|
@ -1487,7 +1519,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||
std::string result = tos(c.resultType);
|
||||
std::string discriminant = tos(c.discriminantType);
|
||||
|
||||
return result + " ~ if isSingleton D then ~D else unknown where D = " + discriminant;
|
||||
if (c.negated)
|
||||
return result + " ~ if isSingleton D then ~D else unknown where D = " + discriminant;
|
||||
else
|
||||
return result + " ~ if isSingleton D then D else unknown where D = " + discriminant;
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
||||
|
@ -1506,28 +1541,8 @@ std::string dump(const Constraint& c)
|
|||
return s;
|
||||
}
|
||||
|
||||
std::string toString(const LValue& lvalue)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauLvaluelessPath);
|
||||
|
||||
std::string s;
|
||||
for (const LValue* current = &lvalue; current; current = baseof(*current))
|
||||
{
|
||||
if (auto field = get<Field>(*current))
|
||||
s = "." + field->key + s;
|
||||
else if (auto symbol = get<Symbol>(*current))
|
||||
s = toString(*symbol) + s;
|
||||
else
|
||||
LUAU_ASSERT(!"Unknown LValue");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
std::optional<std::string> getFunctionNameAsString(const AstExpr& expr)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauLvaluelessPath);
|
||||
|
||||
const AstExpr* curr = &expr;
|
||||
std::string s;
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ Identifier mkName(const AstStatFunction& function)
|
|||
auto name = mkName(*function.name);
|
||||
LUAU_ASSERT(bool(name));
|
||||
if (!name)
|
||||
throwRuntimeError("Internal error: Function declaration has a bad name");
|
||||
throw InternalCompilerError("Internal error: Function declaration has a bad name");
|
||||
|
||||
return *name;
|
||||
}
|
||||
|
@ -256,7 +256,7 @@ struct ArcCollector : public AstVisitor
|
|||
{
|
||||
auto name = mkName(*node->name);
|
||||
if (!name)
|
||||
throwRuntimeError("Internal error: AstStatFunction has a bad name");
|
||||
throw InternalCompilerError("Internal error: AstStatFunction has a bad name");
|
||||
|
||||
add(*name);
|
||||
return true;
|
||||
|
|
|
@ -78,6 +78,42 @@ void TxnLog::concat(TxnLog rhs)
|
|||
typePackChanges[tp] = std::move(rep);
|
||||
}
|
||||
|
||||
void TxnLog::concatAsIntersections(TxnLog rhs, NotNull<TypeArena> arena)
|
||||
{
|
||||
for (auto& [ty, rightRep] : rhs.typeVarChanges)
|
||||
{
|
||||
if (auto leftRep = typeVarChanges.find(ty))
|
||||
{
|
||||
TypeId leftTy = arena->addType((*leftRep)->pending);
|
||||
TypeId rightTy = arena->addType(rightRep->pending);
|
||||
typeVarChanges[ty]->pending.ty = IntersectionTypeVar{{leftTy, rightTy}};
|
||||
}
|
||||
else
|
||||
typeVarChanges[ty] = std::move(rightRep);
|
||||
}
|
||||
|
||||
for (auto& [tp, rep] : rhs.typePackChanges)
|
||||
typePackChanges[tp] = std::move(rep);
|
||||
}
|
||||
|
||||
void TxnLog::concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena)
|
||||
{
|
||||
for (auto& [ty, rightRep] : rhs.typeVarChanges)
|
||||
{
|
||||
if (auto leftRep = typeVarChanges.find(ty))
|
||||
{
|
||||
TypeId leftTy = arena->addType((*leftRep)->pending);
|
||||
TypeId rightTy = arena->addType(rightRep->pending);
|
||||
typeVarChanges[ty]->pending.ty = UnionTypeVar{{leftTy, rightTy}};
|
||||
}
|
||||
else
|
||||
typeVarChanges[ty] = std::move(rightRep);
|
||||
}
|
||||
|
||||
for (auto& [tp, rep] : rhs.typePackChanges)
|
||||
typePackChanges[tp] = std::move(rep);
|
||||
}
|
||||
|
||||
void TxnLog::commit()
|
||||
{
|
||||
for (auto& [ty, rep] : typeVarChanges)
|
||||
|
|
|
@ -341,7 +341,7 @@ public:
|
|||
AstType* operator()(const NegationTypeVar& ntv)
|
||||
{
|
||||
// FIXME: do the same thing we do with ErrorTypeVar
|
||||
throwRuntimeError("Cannot convert NegationTypeVar into AstNode");
|
||||
throw InternalCompilerError("Cannot convert NegationTypeVar into AstNode");
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -295,11 +295,11 @@ struct TypeChecker2
|
|||
Scope* scope = findInnermostScope(ret->location);
|
||||
TypePackId expectedRetType = scope->returnType;
|
||||
|
||||
TypeArena arena;
|
||||
TypePackId actualRetType = reconstructPack(ret->list, arena);
|
||||
TypeArena* arena = &module->internalTypes;
|
||||
TypePackId actualRetType = reconstructPack(ret->list, *arena);
|
||||
|
||||
UnifierSharedState sharedState{&ice};
|
||||
Normalizer normalizer{&arena, singletonTypes, NotNull{&sharedState}};
|
||||
Normalizer normalizer{arena, singletonTypes, NotNull{&sharedState}};
|
||||
Unifier u{NotNull{&normalizer}, Mode::Strict, stack.back(), ret->location, Covariant};
|
||||
|
||||
u.tryUnify(actualRetType, expectedRetType);
|
||||
|
@ -424,13 +424,13 @@ struct TypeChecker2
|
|||
TypePackId iteratorPack = arena.addTypePack(valueTypes, iteratorTail);
|
||||
|
||||
// ... and then expand it out to 3 values (if possible)
|
||||
const std::vector<TypeId> iteratorTypes = flatten(arena, singletonTypes, iteratorPack, 3);
|
||||
if (iteratorTypes.empty())
|
||||
TypePack iteratorTypes = extendTypePack(arena, singletonTypes, iteratorPack, 3);
|
||||
if (iteratorTypes.head.empty())
|
||||
{
|
||||
reportError(GenericError{"for..in loops require at least one value to iterate over. Got zero"}, getLocation(forInStatement->values));
|
||||
return;
|
||||
}
|
||||
TypeId iteratorTy = follow(iteratorTypes[0]);
|
||||
TypeId iteratorTy = follow(iteratorTypes.head[0]);
|
||||
|
||||
auto checkFunction = [this, &arena, &scope, &forInStatement, &variableTypes](
|
||||
const FunctionTypeVar* iterFtv, std::vector<TypeId> iterTys, bool isMm) {
|
||||
|
@ -445,8 +445,8 @@ struct TypeChecker2
|
|||
}
|
||||
|
||||
// It is okay if there aren't enough iterators, but the iteratee must provide enough.
|
||||
std::vector<TypeId> expectedVariableTypes = flatten(arena, singletonTypes, iterFtv->retTypes, variableTypes.size());
|
||||
if (expectedVariableTypes.size() < variableTypes.size())
|
||||
TypePack expectedVariableTypes = extendTypePack(arena, singletonTypes, iterFtv->retTypes, variableTypes.size());
|
||||
if (expectedVariableTypes.head.size() < variableTypes.size())
|
||||
{
|
||||
if (isMm)
|
||||
reportError(
|
||||
|
@ -455,8 +455,8 @@ struct TypeChecker2
|
|||
reportError(GenericError{"next() does not return enough values"}, forInStatement->values.data[0]->location);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < std::min(expectedVariableTypes.size(), variableTypes.size()); ++i)
|
||||
reportErrors(tryUnify(scope, forInStatement->vars.data[i]->location, variableTypes[i], expectedVariableTypes[i]));
|
||||
for (size_t i = 0; i < std::min(expectedVariableTypes.head.size(), variableTypes.size()); ++i)
|
||||
reportErrors(tryUnify(scope, forInStatement->vars.data[i]->location, variableTypes[i], expectedVariableTypes.head[i]));
|
||||
|
||||
// nextFn is going to be invoked with (arrayTy, startIndexTy)
|
||||
|
||||
|
@ -477,25 +477,25 @@ struct TypeChecker2
|
|||
if (maxCount && *maxCount < 2)
|
||||
reportError(CountMismatch{2, std::nullopt, *maxCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||
|
||||
const std::vector<TypeId> flattenedArgTypes = flatten(arena, singletonTypes, iterFtv->argTypes, 2);
|
||||
TypePack flattenedArgTypes = extendTypePack(arena, singletonTypes, iterFtv->argTypes, 2);
|
||||
size_t firstIterationArgCount = iterTys.empty() ? 0 : iterTys.size() - 1;
|
||||
size_t actualArgCount = expectedVariableTypes.size();
|
||||
size_t actualArgCount = expectedVariableTypes.head.size();
|
||||
|
||||
if (firstIterationArgCount < minCount)
|
||||
reportError(CountMismatch{2, std::nullopt, firstIterationArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||
else if (actualArgCount < minCount)
|
||||
reportError(CountMismatch{2, std::nullopt, actualArgCount, CountMismatch::Arg}, forInStatement->vars.data[0]->location);
|
||||
|
||||
if (iterTys.size() >= 2 && flattenedArgTypes.size() > 0)
|
||||
if (iterTys.size() >= 2 && flattenedArgTypes.head.size() > 0)
|
||||
{
|
||||
size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0;
|
||||
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[1], flattenedArgTypes[0]));
|
||||
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[1], flattenedArgTypes.head[0]));
|
||||
}
|
||||
|
||||
if (iterTys.size() == 3 && flattenedArgTypes.size() > 1)
|
||||
if (iterTys.size() == 3 && flattenedArgTypes.head.size() > 1)
|
||||
{
|
||||
size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0;
|
||||
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[2], flattenedArgTypes[1]));
|
||||
reportErrors(tryUnify(scope, forInStatement->values.data[valueIndex]->location, iterTys[2], flattenedArgTypes.head[1]));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -516,7 +516,7 @@ struct TypeChecker2
|
|||
*/
|
||||
if (const FunctionTypeVar* nextFn = get<FunctionTypeVar>(iteratorTy))
|
||||
{
|
||||
checkFunction(nextFn, iteratorTypes, false);
|
||||
checkFunction(nextFn, iteratorTypes.head, false);
|
||||
}
|
||||
else if (const TableTypeVar* ttv = get<TableTypeVar>(iteratorTy))
|
||||
{
|
||||
|
@ -545,19 +545,19 @@ struct TypeChecker2
|
|||
TypePackId argPack = arena.addTypePack({iteratorTy});
|
||||
reportErrors(tryUnify(scope, forInStatement->values.data[0]->location, argPack, iterMmFtv->argTypes));
|
||||
|
||||
std::vector<TypeId> mmIteratorTypes = flatten(arena, singletonTypes, iterMmFtv->retTypes, 3);
|
||||
TypePack mmIteratorTypes = extendTypePack(arena, singletonTypes, iterMmFtv->retTypes, 3);
|
||||
|
||||
if (mmIteratorTypes.size() == 0)
|
||||
if (mmIteratorTypes.head.size() == 0)
|
||||
{
|
||||
reportError(GenericError{"__iter must return at least one value"}, forInStatement->values.data[0]->location);
|
||||
return;
|
||||
}
|
||||
|
||||
TypeId nextFn = follow(mmIteratorTypes[0]);
|
||||
TypeId nextFn = follow(mmIteratorTypes.head[0]);
|
||||
|
||||
if (std::optional<TypeId> instantiatedNextFn = instantiation.substitute(nextFn))
|
||||
{
|
||||
std::vector<TypeId> instantiatedIteratorTypes = mmIteratorTypes;
|
||||
std::vector<TypeId> instantiatedIteratorTypes = mmIteratorTypes.head;
|
||||
instantiatedIteratorTypes[0] = *instantiatedNextFn;
|
||||
|
||||
if (const FunctionTypeVar* nextFtv = get<FunctionTypeVar>(*instantiatedNextFn))
|
||||
|
@ -800,8 +800,8 @@ struct TypeChecker2
|
|||
for (AstExpr* arg : call->args)
|
||||
visit(arg);
|
||||
|
||||
TypeArena arena;
|
||||
Instantiation instantiation{TxnLog::empty(), &arena, TypeLevel{}, stack.back()};
|
||||
TypeArena* arena = &module->internalTypes;
|
||||
Instantiation instantiation{TxnLog::empty(), arena, TypeLevel{}, stack.back()};
|
||||
|
||||
TypePackId expectedRetType = lookupPack(call);
|
||||
TypeId functionType = lookupType(call->func);
|
||||
|
@ -845,30 +845,70 @@ struct TypeChecker2
|
|||
return;
|
||||
}
|
||||
}
|
||||
else if (auto utv = get<UnionTypeVar>(functionType))
|
||||
{
|
||||
// Sometimes it's okay to call a union of functions, but only if all of the functions are the same.
|
||||
std::optional<TypeId> fst;
|
||||
for (TypeId ty : utv)
|
||||
{
|
||||
if (!fst)
|
||||
fst = follow(ty);
|
||||
else if (fst != follow(ty))
|
||||
{
|
||||
reportError(CannotCallNonFunction{functionType}, call->func->location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fst)
|
||||
ice.ice("UnionTypeVar had no elements, so fst is nullopt?");
|
||||
|
||||
if (std::optional<TypeId> instantiatedFunctionType = instantiation.substitute(*fst))
|
||||
{
|
||||
testFunctionType = *instantiatedFunctionType;
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(UnificationTooComplex{}, call->func->location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(CannotCallNonFunction{functionType}, call->func->location);
|
||||
return;
|
||||
}
|
||||
|
||||
for (AstExpr* arg : call->args)
|
||||
{
|
||||
TypeId argTy = lookupType(arg);
|
||||
args.head.push_back(argTy);
|
||||
}
|
||||
|
||||
if (call->self)
|
||||
{
|
||||
AstExprIndexName* indexExpr = call->func->as<AstExprIndexName>();
|
||||
if (!indexExpr)
|
||||
ice.ice("method call expression has no 'self'");
|
||||
|
||||
args.head.insert(args.head.begin(), lookupType(indexExpr->expr));
|
||||
args.head.push_back(lookupType(indexExpr->expr));
|
||||
}
|
||||
|
||||
TypePackId argsTp = arena.addTypePack(args);
|
||||
for (size_t i = 0; i < call->args.size; ++i)
|
||||
{
|
||||
AstExpr* arg = call->args.data[i];
|
||||
TypeId* argTy = module->astTypes.find(arg);
|
||||
if (argTy)
|
||||
args.head.push_back(*argTy);
|
||||
else if (i == call->args.size - 1)
|
||||
{
|
||||
TypePackId* argTail = module->astTypePacks.find(arg);
|
||||
if (argTail)
|
||||
args.tail = *argTail;
|
||||
else
|
||||
args.tail = singletonTypes->anyTypePack;
|
||||
}
|
||||
else
|
||||
args.head.push_back(singletonTypes->anyType);
|
||||
}
|
||||
|
||||
TypePackId argsTp = arena->addTypePack(args);
|
||||
FunctionTypeVar ftv{argsTp, expectedRetType};
|
||||
TypeId expectedType = arena.addType(ftv);
|
||||
TypeId expectedType = arena->addType(ftv);
|
||||
|
||||
if (!isSubtype(testFunctionType, expectedType, stack.back()))
|
||||
{
|
||||
|
@ -881,19 +921,7 @@ struct TypeChecker2
|
|||
void visit(AstExprIndexName* indexName)
|
||||
{
|
||||
TypeId leftType = lookupType(indexName->expr);
|
||||
TypeId resultType = lookupType(indexName);
|
||||
|
||||
// leftType must have a property called indexName->index
|
||||
|
||||
std::optional<TypeId> ty =
|
||||
getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
|
||||
if (ty)
|
||||
{
|
||||
if (!isSubtype(resultType, *ty, stack.back()))
|
||||
{
|
||||
reportError(TypeMismatch{resultType, *ty}, indexName->location);
|
||||
}
|
||||
}
|
||||
getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
|
||||
}
|
||||
|
||||
void visit(AstExprIndexExpr* indexExpr)
|
||||
|
@ -1085,7 +1113,7 @@ struct TypeChecker2
|
|||
|
||||
if (mm)
|
||||
{
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(*mm))
|
||||
if (const FunctionTypeVar* ftv = get<FunctionTypeVar>(follow(*mm)))
|
||||
{
|
||||
TypePackId expectedArgs;
|
||||
// For >= and > we invoke __lt and __le respectively with
|
||||
|
|
|
@ -35,11 +35,12 @@ LUAU_FASTFLAG(LuauTypeNormalization2)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLvaluelessPath, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNilIterator, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeInferMissingFollows, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFollowInLvalueIndexCheck, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
|
@ -47,16 +48,15 @@ LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
|
|||
LUAU_FASTFLAGVARIABLE(LuauOptionalNextKey, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReportShadowedTypeAlias, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterMessagingOnCountMismatch, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauArgMismatchReportFunctionLocation, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIntersectionTestForEquality, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauImplicitElseRefinement, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDeclareClassPrototype, false)
|
||||
LUAU_FASTFLAG(LuauUninhabitedSubAnything)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCallableClasses, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
const char* TimeLimitError_DEPRECATED::what() const throw()
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauIceExceptionInheritanceChange);
|
||||
return "Typeinfer failed to complete in allotted time";
|
||||
}
|
||||
|
||||
static bool typeCouldHaveMetatable(TypeId ty)
|
||||
{
|
||||
|
@ -267,11 +267,6 @@ ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optiona
|
|||
reportErrorCodeTooComplex(module.root->location);
|
||||
return std::move(currentModule);
|
||||
}
|
||||
catch (const RecursionLimitException_DEPRECATED&)
|
||||
{
|
||||
reportErrorCodeTooComplex(module.root->location);
|
||||
return std::move(currentModule);
|
||||
}
|
||||
}
|
||||
|
||||
ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope)
|
||||
|
@ -316,10 +311,6 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
{
|
||||
currentModule->timeout = true;
|
||||
}
|
||||
catch (const TimeLimitError_DEPRECATED&)
|
||||
{
|
||||
currentModule->timeout = true;
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauSharedSelf)
|
||||
{
|
||||
|
@ -356,6 +347,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
|||
unifierState.skipCacheForType.clear();
|
||||
|
||||
duplicateTypeAliases.clear();
|
||||
incorrectClassDefinitions.clear();
|
||||
|
||||
return std::move(currentModule);
|
||||
}
|
||||
|
@ -426,7 +418,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStat& program)
|
|||
ice("Unknown AstStat");
|
||||
|
||||
if (finishTime && TimeTrace::getClock() > *finishTime)
|
||||
throwTimeLimitError();
|
||||
throw TimeLimitError(iceHandler->moduleName);
|
||||
}
|
||||
|
||||
// This particular overload is for do...end. If you need to not increase the scope level, use checkBlock directly.
|
||||
|
@ -453,11 +445,6 @@ void TypeChecker::checkBlock(const ScopePtr& scope, const AstStatBlock& block)
|
|||
reportErrorCodeTooComplex(block.location);
|
||||
return;
|
||||
}
|
||||
catch (const RecursionLimitException_DEPRECATED&)
|
||||
{
|
||||
reportErrorCodeTooComplex(block.location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct InplaceDemoter : TypeVarOnceVisitor
|
||||
|
@ -523,6 +510,10 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A
|
|||
prototype(scope, *typealias, subLevel);
|
||||
++subLevel;
|
||||
}
|
||||
else if (const auto& declaredClass = stat->as<AstStatDeclareClass>(); FFlag::LuauDeclareClassPrototype && declaredClass)
|
||||
{
|
||||
prototype(scope, *declaredClass);
|
||||
}
|
||||
}
|
||||
|
||||
auto protoIter = sorted.begin();
|
||||
|
@ -959,9 +950,9 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assign)
|
|||
|
||||
TypeId right = nullptr;
|
||||
|
||||
Location loc = 0 == assign.values.size ? assign.location
|
||||
: i < assign.values.size ? assign.values.data[i]->location
|
||||
: assign.values.data[assign.values.size - 1]->location;
|
||||
Location loc = 0 == assign.values.size
|
||||
? assign.location
|
||||
: i < assign.values.size ? assign.values.data[i]->location : assign.values.data[assign.values.size - 1]->location;
|
||||
|
||||
if (valueIter != valueEnd)
|
||||
{
|
||||
|
@ -1670,8 +1661,10 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
|
|||
}
|
||||
}
|
||||
|
||||
void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
|
||||
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeclareClassPrototype);
|
||||
|
||||
std::optional<TypeId> superTy = std::nullopt;
|
||||
if (declaredClass.superName)
|
||||
{
|
||||
|
@ -1681,6 +1674,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar
|
|||
if (!lookupType)
|
||||
{
|
||||
reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type});
|
||||
incorrectClassDefinitions.insert(&declaredClass);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1692,7 +1686,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar
|
|||
{
|
||||
reportError(declaredClass.location,
|
||||
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)});
|
||||
|
||||
incorrectClassDefinitions.insert(&declaredClass);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1701,61 +1695,174 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar
|
|||
|
||||
TypeId classTy = addType(ClassTypeVar(className, {}, superTy, std::nullopt, {}, {}, currentModuleName));
|
||||
ClassTypeVar* ctv = getMutable<ClassTypeVar>(classTy);
|
||||
|
||||
TypeId metaTy = addType(TableTypeVar{TableState::Sealed, scope->level});
|
||||
TableTypeVar* metatable = getMutable<TableTypeVar>(metaTy);
|
||||
|
||||
ctv->metatable = metaTy;
|
||||
|
||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
|
||||
}
|
||||
|
||||
for (const AstDeclaredClassProp& prop : declaredClass.props)
|
||||
void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
|
||||
{
|
||||
if (FFlag::LuauDeclareClassPrototype)
|
||||
{
|
||||
Name propName(prop.name.value);
|
||||
TypeId propTy = resolveType(scope, *prop.ty);
|
||||
Name className(declaredClass.name.value);
|
||||
|
||||
bool assignToMetatable = isMetamethod(propName);
|
||||
Luau::ClassTypeVar::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
|
||||
// Don't bother checking if the class definition was incorrect
|
||||
if (incorrectClassDefinitions.find(&declaredClass))
|
||||
return;
|
||||
|
||||
// Function types always take 'self', but this isn't reflected in the
|
||||
// parsed annotation. Add it here.
|
||||
if (prop.isMethod)
|
||||
std::optional<TypeFun> binding;
|
||||
if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end())
|
||||
binding = it->second;
|
||||
|
||||
// This class definition must have been `prototype()`d first.
|
||||
if (!binding)
|
||||
ice("Class not predeclared");
|
||||
|
||||
TypeId classTy = binding->type;
|
||||
ClassTypeVar* ctv = getMutable<ClassTypeVar>(classTy);
|
||||
|
||||
if (!ctv->metatable)
|
||||
ice("No metatable for declared class");
|
||||
|
||||
TableTypeVar* metatable = getMutable<TableTypeVar>(*ctv->metatable);
|
||||
for (const AstDeclaredClassProp& prop : declaredClass.props)
|
||||
{
|
||||
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(propTy))
|
||||
Name propName(prop.name.value);
|
||||
TypeId propTy = resolveType(scope, *prop.ty);
|
||||
|
||||
bool assignToMetatable = isMetamethod(propName);
|
||||
Luau::ClassTypeVar::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
|
||||
|
||||
// Function types always take 'self', but this isn't reflected in the
|
||||
// parsed annotation. Add it here.
|
||||
if (prop.isMethod)
|
||||
{
|
||||
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
|
||||
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
|
||||
ftv->hasSelf = true;
|
||||
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(propTy))
|
||||
{
|
||||
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
|
||||
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
|
||||
ftv->hasSelf = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (assignTo.count(propName) == 0)
|
||||
{
|
||||
assignTo[propName] = {propTy};
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId currentTy = assignTo[propName].type;
|
||||
|
||||
// We special-case this logic to keep the intersection flat; otherwise we
|
||||
// would create a ton of nested intersection types.
|
||||
if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(currentTy))
|
||||
if (assignTo.count(propName) == 0)
|
||||
{
|
||||
std::vector<TypeId> options = itv->parts;
|
||||
options.push_back(propTy);
|
||||
TypeId newItv = addType(IntersectionTypeVar{std::move(options)});
|
||||
|
||||
assignTo[propName] = {newItv};
|
||||
}
|
||||
else if (get<FunctionTypeVar>(currentTy))
|
||||
{
|
||||
TypeId intersection = addType(IntersectionTypeVar{{currentTy, propTy}});
|
||||
|
||||
assignTo[propName] = {intersection};
|
||||
assignTo[propName] = {propTy};
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
|
||||
TypeId currentTy = assignTo[propName].type;
|
||||
|
||||
// We special-case this logic to keep the intersection flat; otherwise we
|
||||
// would create a ton of nested intersection types.
|
||||
if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(currentTy))
|
||||
{
|
||||
std::vector<TypeId> options = itv->parts;
|
||||
options.push_back(propTy);
|
||||
TypeId newItv = addType(IntersectionTypeVar{std::move(options)});
|
||||
|
||||
assignTo[propName] = {newItv};
|
||||
}
|
||||
else if (get<FunctionTypeVar>(currentTy))
|
||||
{
|
||||
TypeId intersection = addType(IntersectionTypeVar{{currentTy, propTy}});
|
||||
|
||||
assignTo[propName] = {intersection};
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<TypeId> superTy = std::nullopt;
|
||||
if (declaredClass.superName)
|
||||
{
|
||||
Name superName = Name(declaredClass.superName->value);
|
||||
std::optional<TypeFun> lookupType = scope->lookupType(superName);
|
||||
|
||||
if (!lookupType)
|
||||
{
|
||||
reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type});
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't have generic classes, so this assertion _should_ never be hit.
|
||||
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
|
||||
superTy = lookupType->type;
|
||||
|
||||
if (!get<ClassTypeVar>(follow(*superTy)))
|
||||
{
|
||||
reportError(declaredClass.location, GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'",
|
||||
superName.c_str(), declaredClass.name.value)});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Name className(declaredClass.name.value);
|
||||
|
||||
TypeId classTy = addType(ClassTypeVar(className, {}, superTy, std::nullopt, {}, {}, currentModuleName));
|
||||
|
||||
ClassTypeVar* ctv = getMutable<ClassTypeVar>(classTy);
|
||||
TypeId metaTy = addType(TableTypeVar{TableState::Sealed, scope->level});
|
||||
TableTypeVar* metatable = getMutable<TableTypeVar>(metaTy);
|
||||
|
||||
ctv->metatable = metaTy;
|
||||
|
||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
|
||||
|
||||
for (const AstDeclaredClassProp& prop : declaredClass.props)
|
||||
{
|
||||
Name propName(prop.name.value);
|
||||
TypeId propTy = resolveType(scope, *prop.ty);
|
||||
|
||||
bool assignToMetatable = isMetamethod(propName);
|
||||
Luau::ClassTypeVar::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
|
||||
|
||||
// Function types always take 'self', but this isn't reflected in the
|
||||
// parsed annotation. Add it here.
|
||||
if (prop.isMethod)
|
||||
{
|
||||
if (FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(propTy))
|
||||
{
|
||||
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
|
||||
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
|
||||
ftv->hasSelf = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (assignTo.count(propName) == 0)
|
||||
{
|
||||
assignTo[propName] = {propTy};
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId currentTy = assignTo[propName].type;
|
||||
|
||||
// We special-case this logic to keep the intersection flat; otherwise we
|
||||
// would create a ton of nested intersection types.
|
||||
if (const IntersectionTypeVar* itv = get<IntersectionTypeVar>(currentTy))
|
||||
{
|
||||
std::vector<TypeId> options = itv->parts;
|
||||
options.push_back(propTy);
|
||||
TypeId newItv = addType(IntersectionTypeVar{std::move(options)});
|
||||
|
||||
assignTo[propName] = {newItv};
|
||||
}
|
||||
else if (get<FunctionTypeVar>(currentTy))
|
||||
{
|
||||
TypeId intersection = addType(IntersectionTypeVar{{currentTy, propTy}});
|
||||
|
||||
assignTo[propName] = {intersection};
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2548,6 +2655,48 @@ static std::optional<std::string> getIdentifierOfBaseVar(AstExpr* node)
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
/** Return true if comparison between the types a and b should be permitted with
|
||||
* the == or ~= operators.
|
||||
*
|
||||
* Two types are considered eligible for equality testing if it is possible for
|
||||
* the test to ever succeed. In other words, we test to see whether the two
|
||||
* types have any overlap at all.
|
||||
*
|
||||
* In order to make things work smoothly with the greedy solver, this function
|
||||
* exempts any and FreeTypeVars from this requirement.
|
||||
*
|
||||
* This function does not (yet?) take into account extra Lua restrictions like
|
||||
* that two tables can only be compared if they have the same metatable. That
|
||||
* is presently handled by the caller.
|
||||
*
|
||||
* @return True if the types are comparable. False if they are not.
|
||||
*
|
||||
* If an internal recursion limit is reached while performing this test, the
|
||||
* function returns std::nullopt.
|
||||
*/
|
||||
static std::optional<bool> areEqComparable(NotNull<TypeArena> arena, NotNull<Normalizer> normalizer, TypeId a, TypeId b)
|
||||
{
|
||||
a = follow(a);
|
||||
b = follow(b);
|
||||
|
||||
auto isExempt = [](TypeId t) {
|
||||
return isNil(t) || get<FreeTypeVar>(t);
|
||||
};
|
||||
|
||||
if (isExempt(a) || isExempt(b))
|
||||
return true;
|
||||
|
||||
TypeId c = arena->addType(IntersectionTypeVar{{a, b}});
|
||||
const NormalizedType* n = normalizer->normalize(c);
|
||||
if (!n)
|
||||
return std::nullopt;
|
||||
|
||||
if (FFlag::LuauUninhabitedSubAnything)
|
||||
return normalizer->isInhabited(n);
|
||||
else
|
||||
return isInhabited_DEPRECATED(*n);
|
||||
}
|
||||
|
||||
TypeId TypeChecker::checkRelationalOperation(
|
||||
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates)
|
||||
{
|
||||
|
@ -2618,6 +2767,28 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
return booleanType;
|
||||
}
|
||||
|
||||
if (FFlag::LuauIntersectionTestForEquality && isEquality)
|
||||
{
|
||||
// Unless either type is free or any, an equality comparison is only
|
||||
// valid when the intersection of the two operands is non-empty.
|
||||
//
|
||||
// eg it is okay to compare string? == number? because the two types
|
||||
// have nil in common, but string == number is not allowed.
|
||||
std::optional<bool> eqTestResult = areEqComparable(NotNull{¤tModule->internalTypes}, NotNull{&normalizer}, lhsType, rhsType);
|
||||
if (!eqTestResult)
|
||||
{
|
||||
reportErrorCodeTooComplex(expr.location);
|
||||
return errorRecoveryType(booleanType);
|
||||
}
|
||||
|
||||
if (!*eqTestResult)
|
||||
{
|
||||
reportError(
|
||||
expr.location, GenericError{format("Type %s cannot be compared with %s", toString(lhsType).c_str(), toString(rhsType).c_str())});
|
||||
return errorRecoveryType(booleanType);
|
||||
}
|
||||
}
|
||||
|
||||
/* Subtlety here:
|
||||
* We need to do this unification first, but there are situations where we don't actually want to
|
||||
* report any problems that might have been surfaced as a result of this step because we might already
|
||||
|
@ -2630,7 +2801,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
state.log.commit();
|
||||
}
|
||||
|
||||
bool needsMetamethod = !isEquality;
|
||||
const bool needsMetamethod = !isEquality;
|
||||
|
||||
TypeId leftType = follow(lhsType);
|
||||
if (get<PrimitiveTypeVar>(leftType) || get<AnyTypeVar>(leftType) || get<ErrorTypeVar>(leftType) || get<UnionTypeVar>(leftType))
|
||||
|
@ -3212,6 +3383,9 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
|||
|
||||
TypeId indexType = checkExpr(scope, *expr.index).type;
|
||||
|
||||
if (FFlag::LuauFollowInLvalueIndexCheck)
|
||||
exprType = follow(exprType);
|
||||
|
||||
if (get<AnyTypeVar>(exprType) || get<ErrorTypeVar>(exprType))
|
||||
return exprType;
|
||||
|
||||
|
@ -3233,6 +3407,16 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
|||
return prop->type;
|
||||
}
|
||||
}
|
||||
else if (FFlag::LuauAllowIndexClassParameters)
|
||||
{
|
||||
if (const ClassTypeVar* exprClass = get<ClassTypeVar>(exprType))
|
||||
{
|
||||
if (isNonstrictMode())
|
||||
return unknownType;
|
||||
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
}
|
||||
|
||||
TableTypeVar* exprTable = getMutableTableType(exprType);
|
||||
|
||||
|
@ -3687,16 +3871,8 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
|||
|
||||
std::string namePath;
|
||||
|
||||
if (FFlag::LuauLvaluelessPath)
|
||||
{
|
||||
if (std::optional<std::string> path = getFunctionNameAsString(funName))
|
||||
namePath = *path;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
}
|
||||
if (std::optional<std::string> path = getFunctionNameAsString(funName))
|
||||
namePath = *path;
|
||||
|
||||
auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack);
|
||||
state.reportError(TypeError{location,
|
||||
|
@ -3806,27 +3982,11 @@ void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funNam
|
|||
|
||||
std::string namePath;
|
||||
|
||||
if (FFlag::LuauLvaluelessPath)
|
||||
{
|
||||
if (std::optional<std::string> path = getFunctionNameAsString(funName))
|
||||
namePath = *path;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (std::optional<LValue> lValue = tryGetLValue(funName))
|
||||
namePath = toString(*lValue);
|
||||
}
|
||||
if (std::optional<std::string> path = getFunctionNameAsString(funName))
|
||||
namePath = *path;
|
||||
|
||||
if (FFlag::LuauArgMismatchReportFunctionLocation)
|
||||
{
|
||||
state.reportError(TypeError{
|
||||
funName.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}});
|
||||
}
|
||||
else
|
||||
{
|
||||
state.reportError(TypeError{
|
||||
state.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}});
|
||||
}
|
||||
state.reportError(TypeError{
|
||||
funName.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}});
|
||||
return;
|
||||
}
|
||||
++paramIter;
|
||||
|
@ -4135,26 +4295,33 @@ std::optional<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const Sc
|
|||
|
||||
std::vector<Location> metaArgLocations;
|
||||
|
||||
// Might be a callable table
|
||||
// Might be a callable table or class
|
||||
std::optional<TypeId> callTy = std::nullopt;
|
||||
if (const MetatableTypeVar* mttv = get<MetatableTypeVar>(fn))
|
||||
{
|
||||
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false))
|
||||
{
|
||||
// Construct arguments with 'self' added in front
|
||||
TypePackId metaCallArgPack = addTypePack(TypePackVar(TypePack{args->head, args->tail}));
|
||||
callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false);
|
||||
}
|
||||
else if (const ClassTypeVar* ctv = get<ClassTypeVar>(fn); FFlag::LuauCallableClasses && ctv && ctv->metatable)
|
||||
{
|
||||
callTy = getIndexTypeFromType(scope, *ctv->metatable, "__call", expr.func->location, /* addErrors= */ false);
|
||||
}
|
||||
|
||||
TypePack* metaCallArgs = getMutable<TypePack>(metaCallArgPack);
|
||||
metaCallArgs->head.insert(metaCallArgs->head.begin(), fn);
|
||||
if (callTy)
|
||||
{
|
||||
// Construct arguments with 'self' added in front
|
||||
TypePackId metaCallArgPack = addTypePack(TypePackVar(TypePack{args->head, args->tail}));
|
||||
|
||||
metaArgLocations = *argLocations;
|
||||
metaArgLocations.insert(metaArgLocations.begin(), expr.func->location);
|
||||
TypePack* metaCallArgs = getMutable<TypePack>(metaCallArgPack);
|
||||
metaCallArgs->head.insert(metaCallArgs->head.begin(), fn);
|
||||
|
||||
fn = instantiate(scope, *ty, expr.func->location);
|
||||
metaArgLocations = *argLocations;
|
||||
metaArgLocations.insert(metaArgLocations.begin(), expr.func->location);
|
||||
|
||||
argPack = metaCallArgPack;
|
||||
args = metaCallArgs;
|
||||
argLocations = &metaArgLocations;
|
||||
}
|
||||
fn = instantiate(scope, *callTy, expr.func->location);
|
||||
|
||||
argPack = metaCallArgPack;
|
||||
args = metaCallArgs;
|
||||
argLocations = &metaArgLocations;
|
||||
}
|
||||
|
||||
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fn);
|
||||
|
@ -4331,7 +4498,7 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast
|
|||
std::string s;
|
||||
for (size_t i = 0; i < overloadTypes.size(); ++i)
|
||||
{
|
||||
TypeId overload = overloadTypes[i];
|
||||
TypeId overload = FFlag::LuauTypeInferMissingFollows ? follow(overloadTypes[i]) : overloadTypes[i];
|
||||
Unifier state = mkUnifier(scope, expr.location);
|
||||
|
||||
// Unify return types
|
||||
|
@ -4712,7 +4879,10 @@ TypePackId TypeChecker::anyifyModuleReturnTypePackGenerics(TypePackId tp)
|
|||
tp = follow(tp);
|
||||
|
||||
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
|
||||
return get<GenericTypeVar>(vtp->ty) ? anyTypePack : tp;
|
||||
{
|
||||
TypeId ty = FFlag::LuauTypeInferMissingFollows ? follow(vtp->ty) : vtp->ty;
|
||||
return get<GenericTypeVar>(ty) ? anyTypePack : tp;
|
||||
}
|
||||
|
||||
if (!get<TypePack>(follow(tp)))
|
||||
return tp;
|
||||
|
@ -4763,19 +4933,6 @@ void TypeChecker::ice(const std::string& message)
|
|||
iceHandler->ice(message);
|
||||
}
|
||||
|
||||
// TODO: Inline me when LuauIceExceptionInheritanceChange is deleted.
|
||||
void TypeChecker::throwTimeLimitError()
|
||||
{
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
throw TimeLimitError(iceHandler->moduleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw TimeLimitError_DEPRECATED();
|
||||
}
|
||||
}
|
||||
|
||||
void TypeChecker::prepareErrorsForDisplay(ErrorVec& errVec)
|
||||
{
|
||||
// Remove errors with names that were generated by recovery from a parse error
|
||||
|
@ -5955,11 +6112,11 @@ void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const Sc
|
|||
if (optionIsSubtype && !targetIsSubtype)
|
||||
return option;
|
||||
else if (!optionIsSubtype && targetIsSubtype)
|
||||
return eqP.type;
|
||||
return FFlag::LuauTypeInferMissingFollows ? follow(eqP.type) : eqP.type;
|
||||
else if (!optionIsSubtype && !targetIsSubtype)
|
||||
return nope;
|
||||
else if (optionIsSubtype && targetIsSubtype)
|
||||
return eqP.type;
|
||||
return FFlag::LuauTypeInferMissingFollows ? follow(eqP.type) : eqP.type;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTxnLogTypePackIterator, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -60,8 +62,8 @@ TypePackIterator::TypePackIterator(TypePackId typePack)
|
|||
}
|
||||
|
||||
TypePackIterator::TypePackIterator(TypePackId typePack, const TxnLog* log)
|
||||
: currentTypePack(follow(typePack))
|
||||
, tp(get<TypePack>(currentTypePack))
|
||||
: currentTypePack(FFlag::LuauTxnLogTypePackIterator ? log->follow(typePack) : follow(typePack))
|
||||
, tp(FFlag::LuauTxnLogTypePackIterator ? log->get<TypePack>(currentTypePack) : get<TypePack>(currentTypePack))
|
||||
, currentIndex(0)
|
||||
, log(log)
|
||||
{
|
||||
|
@ -235,7 +237,7 @@ TypePackId follow(TypePackId tp, std::function<TypePackId(TypePackId)> mapper)
|
|||
cycleTester = nullptr;
|
||||
|
||||
if (tp == cycleTester)
|
||||
throwRuntimeError("Luau::follow detected a TypeVar cycle!!");
|
||||
throw InternalCompilerError("Luau::follow detected a TypeVar cycle!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,10 +122,6 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
|
|||
|
||||
for (TypeId t : utv)
|
||||
{
|
||||
// TODO: we should probably limit recursion here?
|
||||
// RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
// Not needed when we normalize types.
|
||||
if (get<AnyTypeVar>(follow(t)))
|
||||
return t;
|
||||
|
||||
|
@ -164,9 +160,6 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
|
|||
|
||||
for (TypeId t : itv->parts)
|
||||
{
|
||||
// TODO: we should probably limit recursion here?
|
||||
// RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
|
||||
|
||||
if (std::optional<TypeId> ty =
|
||||
getIndexTypeFromType(scope, errors, arena, singletonTypes, t, prop, location, /* addErrors= */ false, handle))
|
||||
parts.push_back(*ty);
|
||||
|
@ -183,7 +176,7 @@ std::optional<TypeId> getIndexTypeFromType(const ScopePtr& scope, ErrorVec& erro
|
|||
if (parts.size() == 1)
|
||||
return parts[0];
|
||||
|
||||
return arena->addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct.
|
||||
return arena->addType(IntersectionTypeVar{std::move(parts)});
|
||||
}
|
||||
|
||||
if (addErrors)
|
||||
|
@ -221,46 +214,95 @@ std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log,
|
|||
return {minCount, minCount + optionalCount};
|
||||
}
|
||||
|
||||
std::vector<TypeId> flatten(TypeArena& arena, NotNull<SingletonTypes> singletonTypes, TypePackId pack, size_t length)
|
||||
TypePack extendTypePack(TypeArena& arena, NotNull<SingletonTypes> singletonTypes, TypePackId pack, size_t length)
|
||||
{
|
||||
std::vector<TypeId> result;
|
||||
TypePack result;
|
||||
|
||||
auto it = begin(pack);
|
||||
auto endIt = end(pack);
|
||||
|
||||
while (it != endIt)
|
||||
while (true)
|
||||
{
|
||||
result.push_back(*it);
|
||||
pack = follow(pack);
|
||||
|
||||
if (result.size() >= length)
|
||||
if (const TypePack* p = get<TypePack>(pack))
|
||||
{
|
||||
size_t i = 0;
|
||||
while (i < p->head.size() && result.head.size() < length)
|
||||
{
|
||||
result.head.push_back(p->head[i]);
|
||||
++i;
|
||||
}
|
||||
|
||||
if (result.head.size() == length)
|
||||
{
|
||||
if (i == p->head.size())
|
||||
result.tail = p->tail;
|
||||
else
|
||||
{
|
||||
TypePackId newTail = arena.addTypePack(TypePack{});
|
||||
TypePack* newTailPack = getMutable<TypePack>(newTail);
|
||||
|
||||
newTailPack->head.insert(newTailPack->head.begin(), p->head.begin() + i, p->head.end());
|
||||
newTailPack->tail = p->tail;
|
||||
|
||||
result.tail = newTail;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else if (p->tail)
|
||||
{
|
||||
pack = *p->tail;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// There just aren't enough types in this pack to satisfy the request.
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(pack))
|
||||
{
|
||||
while (result.head.size() < length)
|
||||
result.head.push_back(vtp->ty);
|
||||
result.tail = pack;
|
||||
return result;
|
||||
}
|
||||
else if (FreeTypePack* ftp = getMutable<FreeTypePack>(pack))
|
||||
{
|
||||
// If we need to get concrete types out of a free pack, we choose to
|
||||
// interpret this as proof that the pack must have at least 'length'
|
||||
// elements. We mint fresh types for each element we're extracting
|
||||
// and rebind the free pack to be a TypePack containing them. We
|
||||
// also have to create a new tail.
|
||||
|
||||
++it;
|
||||
}
|
||||
TypePack newPack;
|
||||
newPack.tail = arena.freshTypePack(ftp->scope);
|
||||
|
||||
if (!it.tail())
|
||||
return result;
|
||||
while (result.head.size() < length)
|
||||
{
|
||||
newPack.head.push_back(arena.freshType(ftp->scope));
|
||||
result.head.push_back(newPack.head.back());
|
||||
}
|
||||
|
||||
TypePackId tail = *it.tail();
|
||||
if (get<TypePack>(tail))
|
||||
LUAU_ASSERT(0);
|
||||
else if (auto vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
while (result.size() < length)
|
||||
result.push_back(vtp->ty);
|
||||
}
|
||||
else if (get<FreeTypePack>(tail) || get<GenericTypePack>(tail))
|
||||
{
|
||||
while (result.size() < length)
|
||||
result.push_back(arena.addType(FreeTypeVar{nullptr}));
|
||||
}
|
||||
else if (auto etp = get<Unifiable::Error>(tail))
|
||||
{
|
||||
while (result.size() < length)
|
||||
result.push_back(singletonTypes->errorRecoveryType());
|
||||
}
|
||||
asMutable(pack)->ty.emplace<TypePack>(std::move(newPack));
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
else if (const Unifiable::Error* etp = getMutable<Unifiable::Error>(pack))
|
||||
{
|
||||
while (result.head.size() < length)
|
||||
result.head.push_back(singletonTypes->errorRecoveryType());
|
||||
|
||||
result.tail = pack;
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the pack is blocked or generic, we can't extract.
|
||||
// Return whatever we've got with this pack as the tail.
|
||||
result.tail = pack;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TypeId> reduceUnion(const std::vector<TypeId>& types)
|
||||
|
|
|
@ -72,7 +72,7 @@ TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper)
|
|||
{
|
||||
TypeId res = ltv->thunk();
|
||||
if (get<LazyTypeVar>(res))
|
||||
throwRuntimeError("Lazy TypeVar cannot resolve to another Lazy TypeVar");
|
||||
throw InternalCompilerError("Lazy TypeVar cannot resolve to another Lazy TypeVar");
|
||||
|
||||
*asMutable(ty) = BoundTypeVar(res);
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ TypeId follow(TypeId t, std::function<TypeId(TypeId)> mapper)
|
|||
cycleTester = nullptr;
|
||||
|
||||
if (t == cycleTester)
|
||||
throwRuntimeError("Luau::follow detected a TypeVar cycle!!");
|
||||
throw InternalCompilerError("Luau::follow detected a TypeVar cycle!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -468,65 +468,65 @@ PendingExpansionTypeVar::PendingExpansionTypeVar(
|
|||
size_t PendingExpansionTypeVar::nextIndex = 0;
|
||||
|
||||
FunctionTypeVar::FunctionTypeVar(TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||
: argTypes(argTypes)
|
||||
: definition(std::move(defn))
|
||||
, argTypes(argTypes)
|
||||
, retTypes(retTypes)
|
||||
, definition(std::move(defn))
|
||||
, hasSelf(hasSelf)
|
||||
{
|
||||
}
|
||||
|
||||
FunctionTypeVar::FunctionTypeVar(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||
: level(level)
|
||||
: definition(std::move(defn))
|
||||
, level(level)
|
||||
, argTypes(argTypes)
|
||||
, retTypes(retTypes)
|
||||
, definition(std::move(defn))
|
||||
, hasSelf(hasSelf)
|
||||
{
|
||||
}
|
||||
|
||||
FunctionTypeVar::FunctionTypeVar(
|
||||
TypeLevel level, Scope* scope, TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||
: level(level)
|
||||
: definition(std::move(defn))
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
, argTypes(argTypes)
|
||||
, retTypes(retTypes)
|
||||
, definition(std::move(defn))
|
||||
, hasSelf(hasSelf)
|
||||
{
|
||||
}
|
||||
|
||||
FunctionTypeVar::FunctionTypeVar(std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes, TypePackId retTypes,
|
||||
std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||
: generics(generics)
|
||||
: definition(std::move(defn))
|
||||
, generics(generics)
|
||||
, genericPacks(genericPacks)
|
||||
, argTypes(argTypes)
|
||||
, retTypes(retTypes)
|
||||
, definition(std::move(defn))
|
||||
, hasSelf(hasSelf)
|
||||
{
|
||||
}
|
||||
|
||||
FunctionTypeVar::FunctionTypeVar(TypeLevel level, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks, TypePackId argTypes,
|
||||
TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||
: level(level)
|
||||
: definition(std::move(defn))
|
||||
, generics(generics)
|
||||
, genericPacks(genericPacks)
|
||||
, level(level)
|
||||
, argTypes(argTypes)
|
||||
, retTypes(retTypes)
|
||||
, definition(std::move(defn))
|
||||
, hasSelf(hasSelf)
|
||||
{
|
||||
}
|
||||
|
||||
FunctionTypeVar::FunctionTypeVar(TypeLevel level, Scope* scope, std::vector<TypeId> generics, std::vector<TypePackId> genericPacks,
|
||||
TypePackId argTypes, TypePackId retTypes, std::optional<FunctionDefinition> defn, bool hasSelf)
|
||||
: level(level)
|
||||
, scope(scope)
|
||||
: definition(std::move(defn))
|
||||
, generics(generics)
|
||||
, genericPacks(genericPacks)
|
||||
, level(level)
|
||||
, scope(scope)
|
||||
, argTypes(argTypes)
|
||||
, retTypes(retTypes)
|
||||
, definition(std::move(defn))
|
||||
, hasSelf(hasSelf)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ void* pagedAllocate(size_t size)
|
|||
// On Linux, we must use mmap because using regular heap results in mprotect() fragmenting the page table and us bumping into 64K mmap limit.
|
||||
#ifdef _WIN32
|
||||
return _aligned_malloc(size, kPageSize);
|
||||
#elif defined(__FreeBSD__)
|
||||
return aligned_alloc(kPageSize, size);
|
||||
#else
|
||||
return mmap(nullptr, pageAlign(size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#endif
|
||||
|
@ -61,6 +63,8 @@ void pagedDeallocate(void* ptr, size_t size)
|
|||
|
||||
#ifdef _WIN32
|
||||
_aligned_free(ptr);
|
||||
#elif defined(__FreeBSD__)
|
||||
free(ptr);
|
||||
#else
|
||||
int rc = munmap(ptr, size);
|
||||
LUAU_ASSERT(rc == 0);
|
||||
|
|
|
@ -22,8 +22,10 @@ LUAU_FASTFLAGVARIABLE(LuauSubtypeNormalizer, false);
|
|||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOverloadedFunctionSubtypingPerf, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything, false)
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||
LUAU_FASTFLAG(LuauTxnLogTypePackIterator)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauNegatedFunctionTypes)
|
||||
|
||||
|
@ -51,6 +53,9 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
|
|||
template<typename TID, typename T>
|
||||
void promote(TID ty, T* t)
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution && !t)
|
||||
return;
|
||||
|
||||
LUAU_ASSERT(t);
|
||||
|
||||
if (useScopes)
|
||||
|
@ -102,6 +107,11 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
|
|||
if (ty->owningArena != typeArena)
|
||||
return false;
|
||||
|
||||
// Surprise, it's actually a BoundTypePack that hasn't been committed yet.
|
||||
// Calling getMutable on this will trigger an assertion.
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2 && !log.is<FunctionTypeVar>(ty))
|
||||
return true;
|
||||
|
||||
promote(ty, log.getMutable<FunctionTypeVar>(ty));
|
||||
return true;
|
||||
}
|
||||
|
@ -115,6 +125,11 @@ struct PromoteTypeLevels final : TypeVarOnceVisitor
|
|||
if (ttv.state != TableState::Free && ttv.state != TableState::Generic)
|
||||
return true;
|
||||
|
||||
// Surprise, it's actually a BoundTypePack that hasn't been committed yet.
|
||||
// Calling getMutable on this will trigger an assertion.
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2 && !log.is<TableTypeVar>(ty))
|
||||
return true;
|
||||
|
||||
promote(ty, log.getMutable<TableTypeVar>(ty));
|
||||
return true;
|
||||
}
|
||||
|
@ -277,7 +292,7 @@ TypeId Widen::clean(TypeId ty)
|
|||
|
||||
TypePackId Widen::clean(TypePackId)
|
||||
{
|
||||
throwRuntimeError("Widen attempted to clean a dirty type pack?");
|
||||
throw InternalCompilerError("Widen attempted to clean a dirty type pack?");
|
||||
}
|
||||
|
||||
bool Widen::ignoreChildren(TypeId ty)
|
||||
|
@ -336,6 +351,20 @@ static bool subsumes(bool useScopes, TY_A* left, TY_B* right)
|
|||
return left->level.subsumes(right->level);
|
||||
}
|
||||
|
||||
TypeMismatch::Context Unifier::mismatchContext()
|
||||
{
|
||||
switch (variance)
|
||||
{
|
||||
case Covariant:
|
||||
return TypeMismatch::CovariantContext;
|
||||
case Invariant:
|
||||
return TypeMismatch::InvariantContext;
|
||||
default:
|
||||
LUAU_ASSERT(false); // This codepath should be unreachable.
|
||||
return TypeMismatch::CovariantContext;
|
||||
}
|
||||
}
|
||||
|
||||
Unifier::Unifier(NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog)
|
||||
: types(normalizer->arena)
|
||||
, singletonTypes(normalizer->singletonTypes)
|
||||
|
@ -559,8 +588,11 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
else if (log.get<NegationTypeVar>(subTy))
|
||||
tryUnifyNegationWithType(subTy, superTy);
|
||||
|
||||
else if (FFlag::LuauUninhabitedSubAnything && !normalizer->isInhabited(subTy))
|
||||
{}
|
||||
|
||||
else
|
||||
reportError(location, TypeMismatch{superTy, subTy});
|
||||
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
|
||||
|
||||
if (cacheEnabled)
|
||||
cacheResult(subTy, superTy, errorCount);
|
||||
|
@ -575,11 +607,16 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* subUnion,
|
|||
std::optional<TypeError> unificationTooComplex;
|
||||
std::optional<TypeError> firstFailedOption;
|
||||
|
||||
std::vector<TxnLog> logs;
|
||||
|
||||
for (TypeId type : subUnion->options)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
innerState.tryUnify_(type, superTy);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
logs.push_back(std::move(innerState.log));
|
||||
|
||||
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
unificationTooComplex = e;
|
||||
else if (!innerState.errors.empty())
|
||||
|
@ -592,51 +629,56 @@ void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionTypeVar* subUnion,
|
|||
}
|
||||
}
|
||||
|
||||
// even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option.
|
||||
auto tryBind = [this, subTy](TypeId superOption) {
|
||||
superOption = log.follow(superOption);
|
||||
|
||||
// just skip if the superOption is not free-ish.
|
||||
auto ttv = log.getMutable<TableTypeVar>(superOption);
|
||||
if (!log.is<FreeTypeVar>(superOption) && (!ttv || ttv->state != TableState::Free))
|
||||
return;
|
||||
|
||||
// If superOption is already present in subTy, do nothing. Nothing new has been learned, but the subtype
|
||||
// test is successful.
|
||||
if (auto subUnion = get<UnionTypeVar>(subTy))
|
||||
{
|
||||
if (end(subUnion) != std::find(begin(subUnion), end(subUnion), superOption))
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we have already checked if S <: T, checking it again will not queue up the type for replacement.
|
||||
// So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set.
|
||||
if (log.haveSeen(subTy, superOption))
|
||||
{
|
||||
// TODO: would it be nice for TxnLog::replace to do this?
|
||||
if (log.is<TableTypeVar>(superOption))
|
||||
log.bindTable(superOption, subTy);
|
||||
else
|
||||
log.replace(superOption, *subTy);
|
||||
}
|
||||
};
|
||||
|
||||
if (auto superUnion = log.getMutable<UnionTypeVar>(superTy))
|
||||
{
|
||||
for (TypeId ty : superUnion)
|
||||
tryBind(ty);
|
||||
}
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
log.concatAsUnion(combineLogsIntoUnion(std::move(logs)), NotNull{types});
|
||||
else
|
||||
tryBind(superTy);
|
||||
{
|
||||
// even if A | B <: T fails, we want to bind some options of T with A | B iff A | B was a subtype of that option.
|
||||
auto tryBind = [this, subTy](TypeId superOption) {
|
||||
superOption = log.follow(superOption);
|
||||
|
||||
// just skip if the superOption is not free-ish.
|
||||
auto ttv = log.getMutable<TableTypeVar>(superOption);
|
||||
if (!log.is<FreeTypeVar>(superOption) && (!ttv || ttv->state != TableState::Free))
|
||||
return;
|
||||
|
||||
// If superOption is already present in subTy, do nothing. Nothing new has been learned, but the subtype
|
||||
// test is successful.
|
||||
if (auto subUnion = get<UnionTypeVar>(subTy))
|
||||
{
|
||||
if (end(subUnion) != std::find(begin(subUnion), end(subUnion), superOption))
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we have already checked if S <: T, checking it again will not queue up the type for replacement.
|
||||
// So we'll have to do it ourselves. We assume they unified cleanly if they are still in the seen set.
|
||||
if (log.haveSeen(subTy, superOption))
|
||||
{
|
||||
// TODO: would it be nice for TxnLog::replace to do this?
|
||||
if (log.is<TableTypeVar>(superOption))
|
||||
log.bindTable(superOption, subTy);
|
||||
else
|
||||
log.replace(superOption, *subTy);
|
||||
}
|
||||
};
|
||||
|
||||
if (auto superUnion = log.getMutable<UnionTypeVar>(superTy))
|
||||
{
|
||||
for (TypeId ty : superUnion)
|
||||
tryBind(ty);
|
||||
}
|
||||
else
|
||||
tryBind(superTy);
|
||||
}
|
||||
|
||||
if (unificationTooComplex)
|
||||
reportError(*unificationTooComplex);
|
||||
else if (failed)
|
||||
{
|
||||
if (firstFailedOption)
|
||||
reportError(location, TypeMismatch{superTy, subTy, "Not all union options are compatible.", *firstFailedOption});
|
||||
reportError(location, TypeMismatch{superTy, subTy, "Not all union options are compatible.", *firstFailedOption, mismatchContext()});
|
||||
else
|
||||
reportError(location, TypeMismatch{superTy, subTy});
|
||||
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -696,6 +738,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<TxnLog> logs;
|
||||
|
||||
for (size_t i = 0; i < uv->options.size(); ++i)
|
||||
{
|
||||
TypeId type = uv->options[(i + startIndex) % uv->options.size()];
|
||||
|
@ -706,9 +750,13 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||
if (innerState.errors.empty())
|
||||
{
|
||||
found = true;
|
||||
log.concat(std::move(innerState.log));
|
||||
|
||||
break;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
logs.push_back(std::move(innerState.log));
|
||||
else
|
||||
{
|
||||
log.concat(std::move(innerState.log));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
{
|
||||
|
@ -723,6 +771,9 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
log.concatAsUnion(combineLogsIntoUnion(std::move(logs)), NotNull{types});
|
||||
|
||||
if (unificationTooComplex)
|
||||
{
|
||||
reportError(*unificationTooComplex);
|
||||
|
@ -744,9 +795,10 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||
else if (!found)
|
||||
{
|
||||
if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
|
||||
reportError(location, TypeMismatch{superTy, subTy, "None of the union options are compatible. For example:", *failedOption});
|
||||
reportError(
|
||||
location, TypeMismatch{superTy, subTy, "None of the union options are compatible. For example:", *failedOption, mismatchContext()});
|
||||
else
|
||||
reportError(location, TypeMismatch{superTy, subTy, "none of the union options are compatible"});
|
||||
reportError(location, TypeMismatch{superTy, subTy, "none of the union options are compatible", mismatchContext()});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -755,6 +807,8 @@ void Unifier::tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const I
|
|||
std::optional<TypeError> unificationTooComplex;
|
||||
std::optional<TypeError> firstFailedOption;
|
||||
|
||||
std::vector<TxnLog> logs;
|
||||
|
||||
// T <: A & B if and only if T <: A and T <: B
|
||||
for (TypeId type : uv->parts)
|
||||
{
|
||||
|
@ -769,13 +823,19 @@ void Unifier::tryUnifyTypeWithIntersection(TypeId subTy, TypeId superTy, const I
|
|||
firstFailedOption = {innerState.errors.front()};
|
||||
}
|
||||
|
||||
log.concat(std::move(innerState.log));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
logs.push_back(std::move(innerState.log));
|
||||
else
|
||||
log.concat(std::move(innerState.log));
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
log.concat(combineLogsIntoIntersection(std::move(logs)));
|
||||
|
||||
if (unificationTooComplex)
|
||||
reportError(*unificationTooComplex);
|
||||
else if (firstFailedOption)
|
||||
reportError(location, TypeMismatch{superTy, subTy, "Not all intersection parts are compatible.", *firstFailedOption});
|
||||
reportError(location, TypeMismatch{superTy, subTy, "Not all intersection parts are compatible.", *firstFailedOption, mismatchContext()});
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeVar* uv, TypeId superTy, bool cacheEnabled, bool isFunctionCall)
|
||||
|
@ -802,6 +862,8 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<TxnLog> logs;
|
||||
|
||||
for (size_t i = 0; i < uv->parts.size(); ++i)
|
||||
{
|
||||
TypeId type = uv->parts[(i + startIndex) % uv->parts.size()];
|
||||
|
@ -812,8 +874,13 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
|
|||
if (innerState.errors.empty())
|
||||
{
|
||||
found = true;
|
||||
log.concat(std::move(innerState.log));
|
||||
break;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
logs.push_back(std::move(innerState.log));
|
||||
else
|
||||
{
|
||||
log.concat(std::move(innerState.log));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
{
|
||||
|
@ -821,6 +888,9 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
log.concat(combineLogsIntoIntersection(std::move(logs)));
|
||||
|
||||
if (unificationTooComplex)
|
||||
reportError(*unificationTooComplex);
|
||||
else if (!found && normalize)
|
||||
|
@ -837,7 +907,7 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionTypeV
|
|||
}
|
||||
else if (!found)
|
||||
{
|
||||
reportError(location, TypeMismatch{superTy, subTy, "none of the intersection parts are compatible"});
|
||||
reportError(location, TypeMismatch{superTy, subTy, "none of the intersection parts are compatible", mismatchContext()});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -849,37 +919,37 @@ void Unifier::tryUnifyNormalizedTypes(
|
|||
if (get<UnknownTypeVar>(superNorm.tops) || get<AnyTypeVar>(superNorm.tops) || get<AnyTypeVar>(subNorm.tops))
|
||||
return;
|
||||
else if (get<UnknownTypeVar>(subNorm.tops))
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
|
||||
if (get<ErrorTypeVar>(subNorm.errors))
|
||||
if (!get<ErrorTypeVar>(superNorm.errors))
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
|
||||
if (get<PrimitiveTypeVar>(subNorm.booleans))
|
||||
{
|
||||
if (!get<PrimitiveTypeVar>(superNorm.booleans))
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
}
|
||||
else if (const SingletonTypeVar* stv = get<SingletonTypeVar>(subNorm.booleans))
|
||||
{
|
||||
if (!get<PrimitiveTypeVar>(superNorm.booleans) && stv != get<SingletonTypeVar>(superNorm.booleans))
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
}
|
||||
|
||||
if (get<PrimitiveTypeVar>(subNorm.nils))
|
||||
if (!get<PrimitiveTypeVar>(superNorm.nils))
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
|
||||
if (get<PrimitiveTypeVar>(subNorm.numbers))
|
||||
if (!get<PrimitiveTypeVar>(superNorm.numbers))
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
|
||||
if (!isSubtype(subNorm.strings, superNorm.strings))
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
|
||||
if (get<PrimitiveTypeVar>(subNorm.threads))
|
||||
if (!get<PrimitiveTypeVar>(superNorm.errors))
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
|
||||
for (TypeId subClass : subNorm.classes)
|
||||
{
|
||||
|
@ -895,7 +965,7 @@ void Unifier::tryUnifyNormalizedTypes(
|
|||
}
|
||||
}
|
||||
if (!found)
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
}
|
||||
|
||||
for (TypeId subTable : subNorm.tables)
|
||||
|
@ -920,19 +990,19 @@ void Unifier::tryUnifyNormalizedTypes(
|
|||
return reportError(*e);
|
||||
}
|
||||
if (!found)
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
}
|
||||
|
||||
if (!subNorm.functions.isNever())
|
||||
{
|
||||
if (superNorm.functions.isNever())
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
for (TypeId superFun : *superNorm.functions.parts)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
const FunctionTypeVar* superFtv = get<FunctionTypeVar>(superFun);
|
||||
if (!superFtv)
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
TypePackId tgt = innerState.tryApplyOverloadedFunction(subTy, subNorm.functions, superFtv->argTypes);
|
||||
innerState.tryUnify_(tgt, superFtv->retTypes);
|
||||
if (innerState.errors.empty())
|
||||
|
@ -940,7 +1010,7 @@ void Unifier::tryUnifyNormalizedTypes(
|
|||
else if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
return reportError(*e);
|
||||
else
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error});
|
||||
return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1306,7 +1376,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
// If both are at the end, we're done
|
||||
if (!superIter.good() && !subIter.good())
|
||||
{
|
||||
if (subTpv->tail && superTpv->tail)
|
||||
if (!FFlag::LuauTxnLogTypePackIterator && subTpv->tail && superTpv->tail)
|
||||
{
|
||||
tryUnify_(*subTpv->tail, *superTpv->tail);
|
||||
break;
|
||||
|
@ -1314,10 +1384,27 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
|
||||
const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr;
|
||||
const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr;
|
||||
if (lFreeTail)
|
||||
if (FFlag::LuauTxnLogTypePackIterator && lFreeTail && rFreeTail)
|
||||
{
|
||||
tryUnify_(*subTpv->tail, *superTpv->tail);
|
||||
}
|
||||
else if (lFreeTail)
|
||||
{
|
||||
tryUnify_(emptyTp, *superTpv->tail);
|
||||
}
|
||||
else if (rFreeTail)
|
||||
{
|
||||
tryUnify_(emptyTp, *subTpv->tail);
|
||||
}
|
||||
else if (FFlag::LuauTxnLogTypePackIterator && subTpv->tail && superTpv->tail)
|
||||
{
|
||||
if (log.getMutable<VariadicTypePack>(superIter.packId))
|
||||
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
|
||||
else if (log.getMutable<VariadicTypePack>(subIter.packId))
|
||||
tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index));
|
||||
else
|
||||
tryUnify_(*subTpv->tail, *superTpv->tail);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -1407,7 +1494,7 @@ void Unifier::tryUnifyPrimitives(TypeId subTy, TypeId superTy)
|
|||
ice("passed non primitive types to unifyPrimitives");
|
||||
|
||||
if (superPrim->type != subPrim->type)
|
||||
reportError(location, TypeMismatch{superTy, subTy});
|
||||
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
|
||||
}
|
||||
|
||||
void Unifier::tryUnifySingletons(TypeId subTy, TypeId superTy)
|
||||
|
@ -1428,7 +1515,7 @@ void Unifier::tryUnifySingletons(TypeId subTy, TypeId superTy)
|
|||
if (superPrim && superPrim->type == PrimitiveTypeVar::String && get<StringSingleton>(subSingleton) && variance == Covariant)
|
||||
return;
|
||||
|
||||
reportError(location, TypeMismatch{superTy, subTy});
|
||||
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall)
|
||||
|
@ -1471,14 +1558,14 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
|||
{
|
||||
numGenerics = std::min(superFunction->generics.size(), subFunction->generics.size());
|
||||
|
||||
reportError(location, TypeMismatch{superTy, subTy, "different number of generic type parameters"});
|
||||
reportError(location, TypeMismatch{superTy, subTy, "different number of generic type parameters", mismatchContext()});
|
||||
}
|
||||
|
||||
if (numGenericPacks != subFunction->genericPacks.size())
|
||||
{
|
||||
numGenericPacks = std::min(superFunction->genericPacks.size(), subFunction->genericPacks.size());
|
||||
|
||||
reportError(location, TypeMismatch{superTy, subTy, "different number of generic type pack parameters"});
|
||||
reportError(location, TypeMismatch{superTy, subTy, "different number of generic type pack parameters", mismatchContext()});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numGenerics; i++)
|
||||
|
@ -1506,9 +1593,9 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
|||
reportError(*e);
|
||||
else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
|
||||
reportError(location, TypeMismatch{superTy, subTy, format("Argument #%d type is not compatible.", *innerState.firstPackErrorPos),
|
||||
innerState.errors.front()});
|
||||
innerState.errors.front(), mismatchContext()});
|
||||
else if (!innerState.errors.empty())
|
||||
reportError(location, TypeMismatch{superTy, subTy, "", innerState.errors.front()});
|
||||
reportError(location, TypeMismatch{superTy, subTy, "", innerState.errors.front(), mismatchContext()});
|
||||
|
||||
innerState.ctx = CountMismatch::FunctionResult;
|
||||
innerState.tryUnify_(subFunction->retTypes, superFunction->retTypes);
|
||||
|
@ -1518,12 +1605,12 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
|
|||
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
reportError(*e);
|
||||
else if (!innerState.errors.empty() && size(superFunction->retTypes) == 1 && finite(superFunction->retTypes))
|
||||
reportError(location, TypeMismatch{superTy, subTy, "Return type is not compatible.", innerState.errors.front()});
|
||||
reportError(location, TypeMismatch{superTy, subTy, "Return type is not compatible.", innerState.errors.front(), mismatchContext()});
|
||||
else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
|
||||
reportError(location, TypeMismatch{superTy, subTy, format("Return #%d type is not compatible.", *innerState.firstPackErrorPos),
|
||||
innerState.errors.front()});
|
||||
innerState.errors.front(), mismatchContext()});
|
||||
else if (!innerState.errors.empty())
|
||||
reportError(location, TypeMismatch{superTy, subTy, "", innerState.errors.front()});
|
||||
reportError(location, TypeMismatch{superTy, subTy, "", innerState.errors.front(), mismatchContext()});
|
||||
}
|
||||
|
||||
log.concat(std::move(innerState.log));
|
||||
|
@ -1700,10 +1787,10 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
// Recursive unification can change the txn log, and invalidate the old
|
||||
// table. If we detect that this has happened, we start over, with the updated
|
||||
// txn log.
|
||||
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner ? log.follow(superTy) : superTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner ? log.follow(subTy) : subTy;
|
||||
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(subTy) : subTy;
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner)
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// If one of the types stopped being a table altogether, we need to restart from the top
|
||||
if ((superTy != superTyNew || subTy != subTyNew) && errors.empty())
|
||||
|
@ -1771,11 +1858,21 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
else
|
||||
extraProperties.push_back(name);
|
||||
|
||||
TypeId superTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(superTy) : superTy;
|
||||
TypeId subTyNew = FFlag::LuauScalarShapeUnifyToMtOwner2 ? log.follow(subTy) : subTy;
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// If one of the types stopped being a table altogether, we need to restart from the top
|
||||
if ((superTy != superTyNew || subTy != subTyNew) && errors.empty())
|
||||
return tryUnify(subTy, superTy, false, isIntersection);
|
||||
}
|
||||
|
||||
// Recursive unification can change the txn log, and invalidate the old
|
||||
// table. If we detect that this has happened, we start over, with the updated
|
||||
// txn log.
|
||||
TableTypeVar* newSuperTable = log.getMutable<TableTypeVar>(superTy);
|
||||
TableTypeVar* newSubTable = log.getMutable<TableTypeVar>(subTy);
|
||||
TableTypeVar* newSuperTable = log.getMutable<TableTypeVar>(superTyNew);
|
||||
TableTypeVar* newSubTable = log.getMutable<TableTypeVar>(subTyNew);
|
||||
if (superTable != newSuperTable || (subTable != newSubTable && subTable != instantiatedSubTable))
|
||||
{
|
||||
if (errors.empty())
|
||||
|
@ -1829,8 +1926,19 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
|||
}
|
||||
|
||||
// Changing the indexer can invalidate the table pointers.
|
||||
superTable = log.getMutable<TableTypeVar>(superTy);
|
||||
subTable = log.getMutable<TableTypeVar>(subTy);
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
superTable = log.getMutable<TableTypeVar>(log.follow(superTy));
|
||||
subTable = log.getMutable<TableTypeVar>(log.follow(subTy));
|
||||
|
||||
if (!superTable || !subTable)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
superTable = log.getMutable<TableTypeVar>(superTy);
|
||||
subTable = log.getMutable<TableTypeVar>(subTy);
|
||||
}
|
||||
|
||||
if (!missingProperties.empty())
|
||||
{
|
||||
|
@ -1872,20 +1980,23 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
|||
TypeId osubTy = subTy;
|
||||
TypeId osuperTy = superTy;
|
||||
|
||||
if (FFlag::LuauUninhabitedSubAnything && !normalizer->isInhabited(subTy))
|
||||
return;
|
||||
|
||||
if (reversed)
|
||||
std::swap(subTy, superTy);
|
||||
|
||||
TableTypeVar* superTable = log.getMutable<TableTypeVar>(superTy);
|
||||
|
||||
if (!superTable || superTable->state != TableState::Free)
|
||||
return reportError(location, TypeMismatch{osuperTy, osubTy});
|
||||
return reportError(location, TypeMismatch{osuperTy, osubTy, mismatchContext()});
|
||||
|
||||
auto fail = [&](std::optional<TypeError> e) {
|
||||
std::string reason = "The former's metatable does not satisfy the requirements.";
|
||||
if (e)
|
||||
reportError(location, TypeMismatch{osuperTy, osubTy, reason, *e});
|
||||
reportError(location, TypeMismatch{osuperTy, osubTy, reason, *e, mismatchContext()});
|
||||
else
|
||||
reportError(location, TypeMismatch{osuperTy, osubTy, reason});
|
||||
reportError(location, TypeMismatch{osuperTy, osubTy, reason, mismatchContext()});
|
||||
};
|
||||
|
||||
// Given t1 where t1 = { lower: (t1) -> (a, b...) }
|
||||
|
@ -1902,7 +2013,7 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
|||
Unifier child = makeChildUnifier();
|
||||
child.tryUnify_(ty, superTy);
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner)
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// To perform subtype <: free table unification, we have tried to unify (subtype's metatable) <: free table
|
||||
// There is a chance that it was unified with the origial subtype, but then, (subtype's metatable) <: subtype could've failed
|
||||
|
@ -1923,7 +2034,7 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
|||
|
||||
log.concat(std::move(child.log));
|
||||
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner)
|
||||
if (FFlag::LuauScalarShapeUnifyToMtOwner2)
|
||||
{
|
||||
// To perform subtype <: free table unification, we have tried to unify (subtype's metatable) <: free table
|
||||
// We return success because subtype <: free table which means that correct unification is to replace free table with the subtype
|
||||
|
@ -1939,7 +2050,7 @@ void Unifier::tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed)
|
|||
}
|
||||
}
|
||||
|
||||
reportError(location, TypeMismatch{osuperTy, osubTy});
|
||||
reportError(location, TypeMismatch{osuperTy, osubTy, mismatchContext()});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1969,7 +2080,7 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
|
|||
if (!superMetatable)
|
||||
ice("tryUnifyMetatable invoked with non-metatable TypeVar");
|
||||
|
||||
TypeError mismatchError = TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy}};
|
||||
TypeError mismatchError = TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, mismatchContext()}};
|
||||
|
||||
if (const MetatableTypeVar* subMetatable = log.getMutable<MetatableTypeVar>(subTy))
|
||||
{
|
||||
|
@ -1980,7 +2091,8 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
|
|||
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
reportError(*e);
|
||||
else if (!innerState.errors.empty())
|
||||
reportError(location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front()});
|
||||
reportError(
|
||||
location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front(), mismatchContext()});
|
||||
|
||||
log.concat(std::move(innerState.log));
|
||||
}
|
||||
|
@ -2017,8 +2129,8 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
|
|||
if (auto e = hasUnificationTooComplex(innerState.errors))
|
||||
reportError(*e);
|
||||
else if (!innerState.errors.empty())
|
||||
reportError(
|
||||
TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front()}});
|
||||
reportError(TypeError{location,
|
||||
TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front(), mismatchContext()}});
|
||||
else if (!missingProperty)
|
||||
{
|
||||
log.concat(std::move(innerState.log));
|
||||
|
@ -2057,9 +2169,9 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
|
|||
|
||||
auto fail = [&]() {
|
||||
if (!reversed)
|
||||
reportError(location, TypeMismatch{superTy, subTy});
|
||||
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
|
||||
else
|
||||
reportError(location, TypeMismatch{subTy, superTy});
|
||||
reportError(location, TypeMismatch{subTy, superTy, mismatchContext()});
|
||||
};
|
||||
|
||||
const ClassTypeVar* superClass = get<ClassTypeVar>(superTy);
|
||||
|
@ -2155,7 +2267,7 @@ void Unifier::tryUnifyTypeWithNegation(TypeId subTy, TypeId superTy)
|
|||
Unifier state = makeChildUnifier();
|
||||
state.tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "");
|
||||
if (state.errors.empty())
|
||||
reportError(location, TypeMismatch{superTy, subTy});
|
||||
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
|
||||
}
|
||||
|
||||
void Unifier::tryUnifyNegationWithType(TypeId subTy, TypeId superTy)
|
||||
|
@ -2165,7 +2277,7 @@ void Unifier::tryUnifyNegationWithType(TypeId subTy, TypeId superTy)
|
|||
ice("tryUnifyNegationWithType subTy must be a negation type");
|
||||
|
||||
// TODO: ~T </: U iff T <: U
|
||||
reportError(location, TypeMismatch{superTy, subTy});
|
||||
reportError(location, TypeMismatch{superTy, subTy, mismatchContext()});
|
||||
}
|
||||
|
||||
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
|
||||
|
@ -2200,9 +2312,11 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
|
|||
if (!superVariadic)
|
||||
ice("passed non-variadic pack to tryUnifyVariadics");
|
||||
|
||||
if (const VariadicTypePack* subVariadic = get<VariadicTypePack>(subTp))
|
||||
if (const VariadicTypePack* subVariadic = FFlag::LuauTxnLogTypePackIterator ? log.get<VariadicTypePack>(subTp) : get<VariadicTypePack>(subTp))
|
||||
{
|
||||
tryUnify_(reversed ? superVariadic->ty : subVariadic->ty, reversed ? subVariadic->ty : superVariadic->ty);
|
||||
else if (get<TypePack>(subTp))
|
||||
}
|
||||
else if (FFlag::LuauTxnLogTypePackIterator ? log.get<TypePack>(subTp) : get<TypePack>(subTp))
|
||||
{
|
||||
TypePackIterator subIter = begin(subTp, &log);
|
||||
TypePackIterator subEnd = end(subTp);
|
||||
|
@ -2350,6 +2464,24 @@ std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, N
|
|||
return Luau::findTablePropertyRespectingMeta(singletonTypes, errors, lhsType, name, location);
|
||||
}
|
||||
|
||||
TxnLog Unifier::combineLogsIntoIntersection(std::vector<TxnLog> logs)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
TxnLog result;
|
||||
for (TxnLog& log : logs)
|
||||
result.concatAsIntersections(std::move(log), NotNull{types});
|
||||
return result;
|
||||
}
|
||||
|
||||
TxnLog Unifier::combineLogsIntoUnion(std::vector<TxnLog> logs)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
TxnLog result;
|
||||
for (TxnLog& log : logs)
|
||||
result.concatAsUnion(std::move(log), NotNull{types});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Unifier::occursCheck(TypeId needle, TypeId haystack)
|
||||
{
|
||||
sharedState.tempSeenTy.clear();
|
||||
|
@ -2491,7 +2623,7 @@ void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, TypeId
|
|||
if (auto e = hasUnificationTooComplex(innerErrors))
|
||||
reportError(*e);
|
||||
else if (!innerErrors.empty())
|
||||
reportError(location, TypeMismatch{wantedType, givenType});
|
||||
reportError(location, TypeMismatch{wantedType, givenType, mismatchContext()});
|
||||
}
|
||||
|
||||
void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const std::string& prop, TypeId wantedType, TypeId givenType)
|
||||
|
@ -2499,8 +2631,8 @@ void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const s
|
|||
if (auto e = hasUnificationTooComplex(innerErrors))
|
||||
reportError(*e);
|
||||
else if (!innerErrors.empty())
|
||||
reportError(
|
||||
TypeError{location, TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible.", prop.c_str()), innerErrors.front()}});
|
||||
reportError(TypeError{location,
|
||||
TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible.", prop.c_str()), innerErrors.front(), mismatchContext()}});
|
||||
}
|
||||
|
||||
void Unifier::ice(const std::string& message, const Location& location)
|
||||
|
|
|
@ -50,6 +50,20 @@ struct Position
|
|||
{
|
||||
return *this == rhs || *this > rhs;
|
||||
}
|
||||
|
||||
void shift(const Position& start, const Position& oldEnd, const Position& newEnd)
|
||||
{
|
||||
if (*this >= start)
|
||||
{
|
||||
if (this->line > oldEnd.line)
|
||||
this->line += (newEnd.line - oldEnd.line);
|
||||
else
|
||||
{
|
||||
this->line = newEnd.line;
|
||||
this->column += (newEnd.column - oldEnd.column);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Location
|
||||
|
@ -93,6 +107,10 @@ struct Location
|
|||
{
|
||||
return begin <= l.begin && end >= l.end;
|
||||
}
|
||||
bool overlaps(const Location& l) const
|
||||
{
|
||||
return (begin <= l.begin && end >= l.begin) || (begin <= l.end && end >= l.end) || (begin >= l.begin && end <= l.end);
|
||||
}
|
||||
bool contains(const Position& p) const
|
||||
{
|
||||
return begin <= p && p < end;
|
||||
|
@ -101,6 +119,18 @@ struct Location
|
|||
{
|
||||
return begin <= p && p <= end;
|
||||
}
|
||||
void extend(const Location& other)
|
||||
{
|
||||
if (other.begin < begin)
|
||||
begin = other.begin;
|
||||
if (other.end > end)
|
||||
end = other.end;
|
||||
}
|
||||
void shift(const Position& start, const Position& oldEnd, const Position& newEnd)
|
||||
{
|
||||
begin.shift(start, oldEnd, newEnd);
|
||||
end.shift(start, oldEnd, newEnd);
|
||||
}
|
||||
};
|
||||
|
||||
std::string toString(const Position& position);
|
||||
|
|
|
@ -24,9 +24,6 @@ LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCommaParenWarnings, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableConstructorRecovery, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauParserErrorsOnMissingDefaultTypePackArgument, false)
|
||||
|
||||
bool lua_telemetry_parsed_out_of_range_bin_integer = false;
|
||||
|
@ -1084,7 +1081,7 @@ void Parser::parseExprList(TempVector<AstExpr*>& result)
|
|||
{
|
||||
nextLexeme();
|
||||
|
||||
if (FFlag::LuauCommaParenWarnings && lexer.current().type == ')')
|
||||
if (lexer.current().type == ')')
|
||||
{
|
||||
report(lexer.current().location, "Expected expression after ',' but got ')' instead");
|
||||
break;
|
||||
|
@ -1179,7 +1176,7 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
|
|||
|
||||
nextLexeme();
|
||||
|
||||
if (FFlag::LuauCommaParenWarnings && lexer.current().type == ')')
|
||||
if (lexer.current().type == ')')
|
||||
{
|
||||
report(lexer.current().location, "Expected type after ',' but got ')' instead");
|
||||
break;
|
||||
|
@ -2317,8 +2314,7 @@ AstExpr* Parser::parseTableConstructor()
|
|||
|
||||
while (lexer.current().type != '}')
|
||||
{
|
||||
if (FFlag::LuauTableConstructorRecovery)
|
||||
lastElementIndent = lexer.current().location.begin.column;
|
||||
lastElementIndent = lexer.current().location.begin.column;
|
||||
|
||||
if (lexer.current().type == '[')
|
||||
{
|
||||
|
@ -2364,8 +2360,7 @@ AstExpr* Parser::parseTableConstructor()
|
|||
{
|
||||
nextLexeme();
|
||||
}
|
||||
else if (FFlag::LuauTableConstructorRecovery && (lexer.current().type == '[' || lexer.current().type == Lexeme::Name) &&
|
||||
lexer.current().location.begin.column == lastElementIndent)
|
||||
else if ((lexer.current().type == '[' || lexer.current().type == Lexeme::Name) && lexer.current().location.begin.column == lastElementIndent)
|
||||
{
|
||||
report(lexer.current().location, "Expected ',' after table constructor element");
|
||||
}
|
||||
|
@ -2556,7 +2551,7 @@ std::pair<AstArray<AstGenericType>, AstArray<AstGenericTypePack>> Parser::parseG
|
|||
{
|
||||
nextLexeme();
|
||||
|
||||
if (FFlag::LuauCommaParenWarnings && lexer.current().type == '>')
|
||||
if (lexer.current().type == '>')
|
||||
{
|
||||
report(lexer.current().location, "Expected type after ',' but got '>' instead");
|
||||
break;
|
||||
|
|
|
@ -1895,10 +1895,7 @@ void BytecodeBuilder::dumpInstruction(const uint32_t* code, std::string& result,
|
|||
|
||||
case LOP_CAPTURE:
|
||||
formatAppend(result, "CAPTURE %s %c%d\n",
|
||||
LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL"
|
||||
: LUAU_INSN_A(insn) == LCT_REF ? "REF"
|
||||
: LUAU_INSN_A(insn) == LCT_VAL ? "VAL"
|
||||
: "",
|
||||
LUAU_INSN_A(insn) == LCT_UPVAL ? "UPVAL" : LUAU_INSN_A(insn) == LCT_REF ? "REF" : LUAU_INSN_A(insn) == LCT_VAL ? "VAL" : "",
|
||||
LUAU_INSN_A(insn) == LCT_UPVAL ? 'U' : 'R', LUAU_INSN_B(insn));
|
||||
break;
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
|||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMultiAssignmentConflictFix, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -2977,16 +2978,46 @@ struct Compiler
|
|||
|
||||
Visitor visitor(this);
|
||||
|
||||
// mark any registers that are used *after* assignment as conflicting
|
||||
for (size_t i = 0; i < vars.size(); ++i)
|
||||
if (FFlag::LuauMultiAssignmentConflictFix)
|
||||
{
|
||||
const LValue& li = vars[i].lvalue;
|
||||
// mark any registers that are used *after* assignment as conflicting
|
||||
|
||||
if (i < values.size)
|
||||
values.data[i]->visit(&visitor);
|
||||
// first we go through assignments to locals, since they are performed before assignments to other l-values
|
||||
for (size_t i = 0; i < vars.size(); ++i)
|
||||
{
|
||||
const LValue& li = vars[i].lvalue;
|
||||
|
||||
if (li.kind == LValue::Kind_Local)
|
||||
visitor.assigned[li.reg] = true;
|
||||
if (li.kind == LValue::Kind_Local)
|
||||
{
|
||||
if (i < values.size)
|
||||
values.data[i]->visit(&visitor);
|
||||
|
||||
visitor.assigned[li.reg] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// and now we handle all other l-values
|
||||
for (size_t i = 0; i < vars.size(); ++i)
|
||||
{
|
||||
const LValue& li = vars[i].lvalue;
|
||||
|
||||
if (li.kind != LValue::Kind_Local && i < values.size)
|
||||
values.data[i]->visit(&visitor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// mark any registers that are used *after* assignment as conflicting
|
||||
for (size_t i = 0; i < vars.size(); ++i)
|
||||
{
|
||||
const LValue& li = vars[i].lvalue;
|
||||
|
||||
if (i < values.size)
|
||||
values.data[i]->visit(&visitor);
|
||||
|
||||
if (li.kind == LValue::Kind_Local)
|
||||
visitor.assigned[li.reg] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// mark any registers used in trailing expressions as conflicting as well
|
||||
|
|
|
@ -112,7 +112,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/include/Luau/Constraint.h
|
||||
Analysis/include/Luau/ConstraintGraphBuilder.h
|
||||
Analysis/include/Luau/ConstraintSolver.h
|
||||
Analysis/include/Luau/DataFlowGraphBuilder.h
|
||||
Analysis/include/Luau/DataFlowGraph.h
|
||||
Analysis/include/Luau/DcrLogger.h
|
||||
Analysis/include/Luau/Def.h
|
||||
Analysis/include/Luau/Documentation.h
|
||||
|
@ -166,7 +166,7 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/src/Constraint.cpp
|
||||
Analysis/src/ConstraintGraphBuilder.cpp
|
||||
Analysis/src/ConstraintSolver.cpp
|
||||
Analysis/src/DataFlowGraphBuilder.cpp
|
||||
Analysis/src/DataFlowGraph.cpp
|
||||
Analysis/src/DcrLogger.cpp
|
||||
Analysis/src/Def.cpp
|
||||
Analysis/src/EmbeddedBuiltinDefinitions.cpp
|
||||
|
@ -298,15 +298,16 @@ endif()
|
|||
if(TARGET Luau.UnitTest)
|
||||
# Luau.UnitTest Sources
|
||||
target_sources(Luau.UnitTest PRIVATE
|
||||
tests/AstQueryDsl.cpp
|
||||
tests/AstQueryDsl.h
|
||||
tests/ClassFixture.cpp
|
||||
tests/ClassFixture.h
|
||||
tests/ConstraintGraphBuilderFixture.cpp
|
||||
tests/ConstraintGraphBuilderFixture.h
|
||||
tests/Fixture.cpp
|
||||
tests/Fixture.h
|
||||
tests/IostreamOptional.h
|
||||
tests/ScopedFlags.h
|
||||
tests/AstQueryDsl.cpp
|
||||
tests/ConstraintGraphBuilderFixture.cpp
|
||||
tests/Fixture.cpp
|
||||
tests/AssemblyBuilderA64.test.cpp
|
||||
tests/AssemblyBuilderX64.test.cpp
|
||||
tests/AstJsonEncoder.test.cpp
|
||||
tests/AstQuery.test.cpp
|
||||
|
@ -318,7 +319,7 @@ if(TARGET Luau.UnitTest)
|
|||
tests/Config.test.cpp
|
||||
tests/ConstraintSolver.test.cpp
|
||||
tests/CostModel.test.cpp
|
||||
tests/DataFlowGraphBuilder.test.cpp
|
||||
tests/DataFlowGraph.test.cpp
|
||||
tests/Error.test.cpp
|
||||
tests/Frontend.test.cpp
|
||||
tests/JsonEmitter.test.cpp
|
||||
|
|
|
@ -22,6 +22,21 @@ static time_t timegm(struct tm* timep)
|
|||
{
|
||||
return _mkgmtime(timep);
|
||||
}
|
||||
#elif defined(__FreeBSD__)
|
||||
static tm* gmtime_r(const time_t* timep, tm* result)
|
||||
{
|
||||
return gmtime_s(timep, result) == 0 ? result : NULL;
|
||||
}
|
||||
|
||||
static tm* localtime_r(const time_t* timep, tm* result)
|
||||
{
|
||||
return localtime_s(timep, result) == 0 ? result : NULL;
|
||||
}
|
||||
|
||||
static time_t timegm(struct tm* timep)
|
||||
{
|
||||
return mktime(timep);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int os_clock(lua_State* L)
|
||||
|
|
|
@ -91,8 +91,6 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "class_method")
|
|||
|
||||
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "overloaded_class_method")
|
||||
{
|
||||
ScopedFastFlag luauCheckOverloadedDocSymbol{"LuauCheckOverloadedDocSymbol", true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class Foo
|
||||
function bar(self, x: string): number
|
||||
|
@ -127,8 +125,6 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_function_prop")
|
|||
|
||||
TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_overloaded_function_prop")
|
||||
{
|
||||
ScopedFastFlag luauCheckOverloadedDocSymbol{"LuauCheckOverloadedDocSymbol", true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare Foo: {
|
||||
new: ((number) -> string) & ((string) -> number)
|
||||
|
|
113
tests/ClassFixture.cpp
Normal file
113
tests/ClassFixture.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "ClassFixture.h"
|
||||
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
using std::nullopt;
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
ClassFixture::ClassFixture()
|
||||
{
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
TypeId numberType = typeChecker.numberType;
|
||||
|
||||
unfreeze(arena);
|
||||
|
||||
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(baseClassInstanceType)->props = {
|
||||
{"BaseMethod", {makeFunction(arena, baseClassInstanceType, {numberType}, {})}},
|
||||
{"BaseField", {numberType}},
|
||||
};
|
||||
|
||||
TypeId baseClassType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(baseClassType)->props = {
|
||||
{"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}},
|
||||
{"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}},
|
||||
{"New", {makeFunction(arena, nullopt, {}, {baseClassInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||
addGlobalBinding(frontend, "BaseClass", baseClassType, "@test");
|
||||
|
||||
TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassTypeVar>(childClassInstanceType)->props = {
|
||||
{"Method", {makeFunction(arena, childClassInstanceType, {}, {typeChecker.stringType})}},
|
||||
};
|
||||
|
||||
TypeId childClassType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(childClassType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
addGlobalBinding(frontend, "ChildClass", childClassType, "@test");
|
||||
|
||||
TypeId grandChildInstanceType = arena.addType(ClassTypeVar{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassTypeVar>(grandChildInstanceType)->props = {
|
||||
{"Method", {makeFunction(arena, grandChildInstanceType, {}, {typeChecker.stringType})}},
|
||||
};
|
||||
|
||||
TypeId grandChildType = arena.addType(ClassTypeVar{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(grandChildType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
|
||||
addGlobalBinding(frontend, "GrandChild", childClassType, "@test");
|
||||
|
||||
TypeId anotherChildInstanceType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassTypeVar>(anotherChildInstanceType)->props = {
|
||||
{"Method", {makeFunction(arena, anotherChildInstanceType, {}, {typeChecker.stringType})}},
|
||||
};
|
||||
|
||||
TypeId anotherChildType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(anotherChildType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType};
|
||||
addGlobalBinding(frontend, "AnotherChild", childClassType, "@test");
|
||||
|
||||
TypeId unrelatedClassInstanceType = arena.addType(ClassTypeVar{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
|
||||
TypeId unrelatedClassType = arena.addType(ClassTypeVar{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(unrelatedClassType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {unrelatedClassInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["UnrelatedClass"] = TypeFun{{}, unrelatedClassInstanceType};
|
||||
addGlobalBinding(frontend, "UnrelatedClass", unrelatedClassType, "@test");
|
||||
|
||||
TypeId vector2MetaType = arena.addType(TableTypeVar{});
|
||||
|
||||
TypeId vector2InstanceType = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(vector2InstanceType)->props = {
|
||||
{"X", {numberType}},
|
||||
{"Y", {numberType}},
|
||||
};
|
||||
|
||||
TypeId vector2Type = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(vector2Type)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}},
|
||||
};
|
||||
getMutable<TableTypeVar>(vector2MetaType)->props = {
|
||||
{"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType};
|
||||
addGlobalBinding(frontend, "Vector2", vector2Type, "@test");
|
||||
|
||||
TypeId callableClassMetaType = arena.addType(TableTypeVar{});
|
||||
TypeId callableClassType = arena.addType(ClassTypeVar{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test"});
|
||||
getMutable<TableTypeVar>(callableClassMetaType)->props = {
|
||||
{"__call", {makeFunction(arena, nullopt, {callableClassType, typeChecker.stringType}, {typeChecker.numberType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["CallableClass"] = TypeFun{{}, callableClassType};
|
||||
|
||||
for (const auto& [name, tf] : typeChecker.globalScope->exportedTypeBindings)
|
||||
persist(tf.type);
|
||||
|
||||
freeze(arena);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
13
tests/ClassFixture.h
Normal file
13
tests/ClassFixture.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct ClassFixture : BuiltinsFixture
|
||||
{
|
||||
ClassFixture();
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -6751,6 +6751,21 @@ ADD R4 R0 R1
|
|||
MOVE R0 R2
|
||||
MOVE R1 R3
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
ScopedFastFlag luauMultiAssignmentConflictFix{"LuauMultiAssignmentConflictFix", true};
|
||||
|
||||
// because we perform assignments to complex l-values after assignments to locals, we make sure register conflicts are tracked accordingly
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
local a, b = ...
|
||||
a[1], b = b, b + 1
|
||||
)"),
|
||||
R"(
|
||||
GETVARARGS R0 2
|
||||
ADDK R2 R1 K0
|
||||
SETTABLEN R1 R0 1
|
||||
MOVE R1 R2
|
||||
RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/DataFlowGraphBuilder.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Parser.h"
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauIceExceptionInheritanceChange);
|
||||
|
||||
TEST_SUITE_BEGIN("ModuleTests");
|
||||
|
||||
|
@ -279,14 +278,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
|||
TypeArena dest;
|
||||
CloneState cloneState;
|
||||
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException_DEPRECATED);
|
||||
}
|
||||
CHECK_THROWS_AS(clone(table, dest, cloneState), RecursionLimitException);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "any_persistance_does_not_leak")
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
using Luau::NotNull;
|
||||
|
||||
static_assert(!std::is_convertible<NotNull<int>, bool>::value, "NotNull<T> ought not to be convertible into bool");
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
|
|
@ -2723,8 +2723,6 @@ TEST_CASE_FIXTURE(Fixture, "error_message_for_using_function_as_type_annotation"
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the_end_of_a_function_argument_list")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCommaParenWarnings", true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
foo(a, b, c,)
|
||||
)");
|
||||
|
@ -2737,8 +2735,6 @@ TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the_end_of_a_function_parameter_list")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCommaParenWarnings", true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
export type VisitFn = (
|
||||
any,
|
||||
|
@ -2754,8 +2750,6 @@ TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the_end_of_a_generic_parameter_list")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCommaParenWarnings", true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
export type VisitFn = <A, B,>(a: A, b: B) -> ()
|
||||
)");
|
||||
|
@ -2778,8 +2772,6 @@ TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_an_extra_comma_at_the
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "get_a_nice_error_when_there_is_no_comma_between_table_members")
|
||||
{
|
||||
ScopedFastFlag luauTableConstructorRecovery{"LuauTableConstructorRecovery", true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
local t = {
|
||||
first = 1
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
||||
LUAU_FASTFLAG(LuauFunctionReturnStringificationFixup);
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
||||
|
@ -83,7 +82,7 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break")
|
|||
|
||||
ToStringOptions opts;
|
||||
opts.useLineBreaks = true;
|
||||
opts.indent = true;
|
||||
opts.DEPRECATED_indent = true;
|
||||
|
||||
//clang-format off
|
||||
CHECK_EQ("{|\n"
|
||||
|
@ -568,10 +567,7 @@ TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_return_type_if_pack_has_an_emp
|
|||
|
||||
TypeId functionType = arena.addType(FunctionTypeVar{argList, emptyTail});
|
||||
|
||||
if (FFlag::LuauFunctionReturnStringificationFixup)
|
||||
CHECK("(string) -> string" == toString(functionType));
|
||||
else
|
||||
CHECK("(string) -> (string)" == toString(functionType));
|
||||
CHECK("(string) -> string" == toString(functionType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_union")
|
||||
|
|
|
@ -9,6 +9,7 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauNoMoreGlobalSingletonTypes)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
|
@ -199,9 +200,15 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
const char* expectedError = "Type '{ v: string }' could not be converted into 'T<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 'v' is not compatible. Type 'string' could not be converted into 'number'";
|
||||
const char* expectedError;
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
expectedError = "Type '{ v: string }' could not be converted into 'T<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context";
|
||||
else
|
||||
expectedError = "Type '{ v: string }' could not be converted into 'T<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 'v' is not compatible. Type 'string' could not be converted into 'number'";
|
||||
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
|
@ -220,11 +227,19 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
const char* expectedError = "Type '{ t: { v: string } }' could not be converted into 'U<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 't' is not compatible. Type '{ v: string }' could not be converted into 'T<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 'v' is not compatible. Type 'string' could not be converted into 'number'";
|
||||
const char* expectedError;
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
expectedError = "Type '{ t: { v: string } }' could not be converted into 'U<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 't' is not compatible. Type '{ v: string }' could not be converted into 'T<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 'v' is not compatible. Type 'string' could not be converted into 'number' in an invariant context";
|
||||
else
|
||||
expectedError = "Type '{ t: { v: string } }' could not be converted into 'U<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 't' is not compatible. Type '{ v: string }' could not be converted into 'T<number>'\n"
|
||||
"caused by:\n"
|
||||
" Property 'v' is not compatible. Type 'string' could not be converted into 'number'";
|
||||
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
||||
CHECK(toString(result.errors[0]) == expectedError);
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauIceExceptionInheritanceChange)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("AnnotationTests");
|
||||
|
|
|
@ -684,20 +684,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("string", toString(requireType("foo")));
|
||||
CHECK_EQ("*error-type*", toString(requireType("bar")));
|
||||
CHECK_EQ("*error-type*", toString(requireType("baz")));
|
||||
CHECK_EQ("*error-type*", toString(requireType("quux")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("any", toString(requireType("foo")));
|
||||
CHECK_EQ("any", toString(requireType("bar")));
|
||||
CHECK_EQ("any", toString(requireType("baz")));
|
||||
CHECK_EQ("any", toString(requireType("quux")));
|
||||
}
|
||||
CHECK_EQ("any", toString(requireType("foo")));
|
||||
CHECK_EQ("any", toString(requireType("bar")));
|
||||
CHECK_EQ("any", toString(requireType("baz")));
|
||||
CHECK_EQ("any", toString(requireType("quux")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_string_head")
|
||||
|
@ -714,19 +704,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_strin
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("string", toString(requireType("foo")));
|
||||
CHECK_EQ("string", toString(requireType("bar")));
|
||||
CHECK_EQ("*error-type*", toString(requireType("baz")));
|
||||
CHECK_EQ("*error-type*", toString(requireType("quux")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("any", toString(requireType("foo")));
|
||||
CHECK_EQ("any", toString(requireType("bar")));
|
||||
CHECK_EQ("any", toString(requireType("baz")));
|
||||
CHECK_EQ("any", toString(requireType("quux")));
|
||||
}
|
||||
|
||||
CHECK_EQ("any", toString(requireType("bar")));
|
||||
CHECK_EQ("any", toString(requireType("baz")));
|
||||
CHECK_EQ("any", toString(requireType("quux")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_format_as_method")
|
||||
|
|
|
@ -1,102 +1,18 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeVar.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
#include "ClassFixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
using namespace Luau;
|
||||
using std::nullopt;
|
||||
|
||||
struct ClassFixture : BuiltinsFixture
|
||||
{
|
||||
ClassFixture()
|
||||
{
|
||||
TypeArena& arena = typeChecker.globalTypes;
|
||||
TypeId numberType = typeChecker.numberType;
|
||||
|
||||
unfreeze(arena);
|
||||
|
||||
TypeId baseClassInstanceType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(baseClassInstanceType)->props = {
|
||||
{"BaseMethod", {makeFunction(arena, baseClassInstanceType, {numberType}, {})}},
|
||||
{"BaseField", {numberType}},
|
||||
};
|
||||
|
||||
TypeId baseClassType = arena.addType(ClassTypeVar{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(baseClassType)->props = {
|
||||
{"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}},
|
||||
{"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}},
|
||||
{"New", {makeFunction(arena, nullopt, {}, {baseClassInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType};
|
||||
addGlobalBinding(frontend, "BaseClass", baseClassType, "@test");
|
||||
|
||||
TypeId childClassInstanceType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassTypeVar>(childClassInstanceType)->props = {
|
||||
{"Method", {makeFunction(arena, childClassInstanceType, {}, {typeChecker.stringType})}},
|
||||
};
|
||||
|
||||
TypeId childClassType = arena.addType(ClassTypeVar{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(childClassType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType};
|
||||
addGlobalBinding(frontend, "ChildClass", childClassType, "@test");
|
||||
|
||||
TypeId grandChildInstanceType = arena.addType(ClassTypeVar{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassTypeVar>(grandChildInstanceType)->props = {
|
||||
{"Method", {makeFunction(arena, grandChildInstanceType, {}, {typeChecker.stringType})}},
|
||||
};
|
||||
|
||||
TypeId grandChildType = arena.addType(ClassTypeVar{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(grandChildType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType};
|
||||
addGlobalBinding(frontend, "GrandChild", childClassType, "@test");
|
||||
|
||||
TypeId anotherChildInstanceType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassTypeVar>(anotherChildInstanceType)->props = {
|
||||
{"Method", {makeFunction(arena, anotherChildInstanceType, {}, {typeChecker.stringType})}},
|
||||
};
|
||||
|
||||
TypeId anotherChildType = arena.addType(ClassTypeVar{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(anotherChildType)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType};
|
||||
addGlobalBinding(frontend, "AnotherChild", childClassType, "@test");
|
||||
|
||||
TypeId vector2MetaType = arena.addType(TableTypeVar{});
|
||||
|
||||
TypeId vector2InstanceType = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(vector2InstanceType)->props = {
|
||||
{"X", {numberType}},
|
||||
{"Y", {numberType}},
|
||||
};
|
||||
|
||||
TypeId vector2Type = arena.addType(ClassTypeVar{"Vector2", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassTypeVar>(vector2Type)->props = {
|
||||
{"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}},
|
||||
};
|
||||
getMutable<TableTypeVar>(vector2MetaType)->props = {
|
||||
{"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}},
|
||||
};
|
||||
typeChecker.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType};
|
||||
addGlobalBinding(frontend, "Vector2", vector2Type, "@test");
|
||||
|
||||
for (const auto& [name, tf] : typeChecker.globalScope->exportedTypeBindings)
|
||||
persist(tf.type);
|
||||
|
||||
freeze(arena);
|
||||
}
|
||||
};
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferClasses");
|
||||
|
||||
|
@ -514,4 +430,67 @@ TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "index_instance_property")
|
||||
{
|
||||
ScopedFastFlag luauAllowIndexClassParameters{"LuauAllowIndexClassParameters", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function execute(object: BaseClass, name: string)
|
||||
print(object[name])
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ("Attempting a dynamic property access on type 'BaseClass' is unsafe and may cause exceptions at runtime", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict")
|
||||
{
|
||||
ScopedFastFlag luauAllowIndexClassParameters{"LuauAllowIndexClassParameters", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
|
||||
local function execute(object: BaseClass, name: string)
|
||||
print(object[name])
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "type_mismatch_invariance_required_for_error")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type A = { x: ChildClass }
|
||||
type B = { x: BaseClass }
|
||||
|
||||
local a: A
|
||||
local b: B = a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'ChildClass' could not be converted into 'BaseClass' in an invariant context)");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'ChildClass' could not be converted into 'BaseClass')");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "callable_classes")
|
||||
{
|
||||
ScopedFastFlag luauCallableClasses{"LuauCallableClasses", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : CallableClass
|
||||
local y = x("testing")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("number", toString(requireType("y")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -311,8 +311,6 @@ TEST_CASE_FIXTURE(Fixture, "definitions_documentation_symbols")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "definitions_symbols_are_generated_for_recursively_referenced_types")
|
||||
{
|
||||
ScopedFastFlag LuauPersistTypesAfterGeneratingDocSyms("LuauPersistTypesAfterGeneratingDocSyms", true);
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class MyClass
|
||||
function myMethod(self)
|
||||
|
@ -396,4 +394,26 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_string_props")
|
|||
CHECK_EQ(toString(requireType("y")), "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
||||
{
|
||||
ScopedFastFlag LuauDeclareClassPrototype("LuauDeclareClassPrototype", true);
|
||||
|
||||
unfreeze(typeChecker.globalTypes);
|
||||
LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"(
|
||||
declare class Channel
|
||||
Messages: { Message }
|
||||
OnMessage: (message: Message) -> ()
|
||||
end
|
||||
|
||||
declare class Message
|
||||
Text: string
|
||||
Channel: Channel
|
||||
end
|
||||
)",
|
||||
"@test");
|
||||
freeze(typeChecker.globalTypes);
|
||||
|
||||
REQUIRE(result.success);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -230,8 +230,6 @@ TEST_CASE_FIXTURE(Fixture, "too_many_arguments")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "too_many_arguments_error_location")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauArgMismatchReportFunctionLocation", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
||||
|
@ -507,7 +505,9 @@ TEST_CASE_FIXTURE(Fixture, "complicated_return_types_require_an_explicit_annotat
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
const FunctionTypeVar* functionType = get<FunctionTypeVar>(requireType("most_of_the_natural_numbers"));
|
||||
TypeId ty = requireType("most_of_the_natural_numbers");
|
||||
const FunctionTypeVar* functionType = get<FunctionTypeVar>(ty);
|
||||
REQUIRE_MESSAGE(functionType, "Expected function but got " << toString(ty));
|
||||
|
||||
std::optional<TypeId> retType = first(functionType->retTypes);
|
||||
REQUIRE(retType);
|
||||
|
@ -1830,4 +1830,18 @@ TEST_CASE_FIXTURE(Fixture, "other_things_are_not_related_to_function")
|
|||
CHECK(5 == result.errors[3].location.begin.line);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_must_follow_in_overload_resolution")
|
||||
{
|
||||
ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
for _ in function<t0>():(t0)&((()->())&(()->()))
|
||||
end do
|
||||
_(_(_,_,_),_)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -717,12 +718,24 @@ y.a.c = y
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
R"(Type 'y' could not be converted into 'T<string>'
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
R"(Type 'y' could not be converted into 'T<string>'
|
||||
caused by:
|
||||
Property 'a' is not compatible. Type '{ c: T<string>?, d: number }' could not be converted into 'U<string>'
|
||||
caused by:
|
||||
Property 'd' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(result.errors[0]),
|
||||
R"(Type 'y' could not be converted into 'T<string>'
|
||||
caused by:
|
||||
Property 'a' is not compatible. Type '{ c: T<string>?, d: number }' could not be converted into 'U<string>'
|
||||
caused by:
|
||||
Property 'd' is not compatible. Type 'number' could not be converted into 'string')");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification1")
|
||||
|
@ -1249,4 +1262,21 @@ instantiate(function(x: string) return "foo" end)
|
|||
CHECK_EQ("<a>(string) -> string", toString(tm1->givenType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_and_generalization_play_nice")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local foo = function(a)
|
||||
return a()
|
||||
end
|
||||
|
||||
local a = foo(function() return 1 end)
|
||||
local b = foo(function() return "bar" end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK("number" == toString(requireType("a")));
|
||||
CHECK("string" == toString(requireType("b")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -185,7 +185,15 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_dep
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("string & string", toString(requireType("r")));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("string", toString(requireType("r")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("string & string", toString(requireType("r")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
|
||||
|
@ -199,7 +207,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ("number & string", toString(requireType("r"))); // TODO(amccord): This should be an error.
|
||||
CHECK_EQ("number & string", toString(requireType("r")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_part_missing_the_property")
|
||||
|
@ -525,18 +533,16 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
|
|||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
{"LuauUninhabitedSubAnything", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x : { p : number?, q : never } & { p : never, q : string? }
|
||||
local x : { p : number?, q : never } & { p : never, q : string? } -- OK
|
||||
local y : { p : never, q : never } = x -- OK
|
||||
local z : never = x -- OK
|
||||
)");
|
||||
|
||||
// TODO: this should not produce type errors, since never <: { p : never }
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Type '{| p: never, q: string? |} & {| p: number?, q: never |}' could not be converted into 'never'; none "
|
||||
"of the intersection parts are compatible");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
|
||||
|
@ -848,7 +854,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with table")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with_table")
|
||||
{
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
|
@ -902,4 +908,37 @@ TEST_CASE_FIXTURE(Fixture, "CLI-44817")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(t): { x: number } & { x: string }
|
||||
local x = t.x
|
||||
return t
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("({| x: number |} & {| x: string |}) -> {| x: number |} & {| x: string |}", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_intersection_types_2")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(t: { x: number } & { x: string })
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("({| x: number |} & {| x: string |}) -> number & string", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -408,7 +409,12 @@ local b: B.T = a
|
|||
CheckResult result = frontend.check("game/C");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'number' could not be converted into 'string')");
|
||||
}
|
||||
|
@ -442,7 +448,12 @@ local b: B.T = a
|
|||
CheckResult result = frontend.check("game/D");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
|
||||
caused by:
|
||||
Property 'x' is not compatible. Type 'number' could not be converted into 'string')");
|
||||
}
|
||||
|
@ -462,4 +473,15 @@ return l0
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_anyify_variadic_return_must_follow")
|
||||
{
|
||||
ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
return unpack(l0[_])
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Luau/VisitTypeVar.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
#include "ClassFixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
|
@ -817,6 +818,21 @@ TEST_CASE_FIXTURE(Fixture, "operator_eq_operands_are_not_subtypes_of_each_other_
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "operator_eq_completely_incompatible")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIntersectionTestForEquality", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: string | number = "hi"
|
||||
local b: {x: string}? = {x = "bye"}
|
||||
|
||||
local r1 = a == b
|
||||
local r2 = b == a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "refine_and_or")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -916,6 +932,31 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "expected_types_through_binary_or")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ClassFixture, "unrelated_classes_cannot_be_compared")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIntersectionTestForEquality", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a = BaseClass.New()
|
||||
local b = UnrelatedClass.New()
|
||||
|
||||
local c = a == b
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unrelated_primitives_cannot_be_compared")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIntersectionTestForEquality", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local c = 5 == true
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "mm_ops_must_return_a_value")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
|
|
|
@ -170,24 +170,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "error_on_eq_metamethod_returning_a_type_othe
|
|||
CHECK_EQ("Metamethod '__eq' must return type 'boolean'", ge->message);
|
||||
}
|
||||
|
||||
// Requires success typing to confidently determine that this expression has no overlap.
|
||||
TEST_CASE_FIXTURE(Fixture, "operator_eq_completely_incompatible")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a: string | number = "hi"
|
||||
local b: {x: string}? = {x = "bye"}
|
||||
|
||||
local r1 = a == b
|
||||
local r2 = b == a
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
// Belongs in TypeInfer.refinements.test.cpp.
|
||||
// We'll need to not only report an error on `a == b`, but also to refine both operands as `never` in the `==` branch.
|
||||
// We need refine both operands as `never` in the `==` branch.
|
||||
TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauIntersectionTestForEquality", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(a: string, b: boolean?)
|
||||
if a == b then
|
||||
|
@ -198,7 +186,7 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap")
|
|||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "string"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "boolean?"); // a == b
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -35,6 +36,27 @@ std::optional<WithPredicate<TypePackId>> magicFunctionInstanceIsA(
|
|||
return WithPredicate<TypePackId>{booleanPack, {IsAPredicate{std::move(*lvalue), expr.location, tfun->type}}};
|
||||
}
|
||||
|
||||
std::vector<ConnectiveId> dcrMagicRefinementInstanceIsA(MagicRefinementContext ctx)
|
||||
{
|
||||
if (ctx.callSite->args.size != 1)
|
||||
return {};
|
||||
|
||||
auto index = ctx.callSite->func->as<Luau::AstExprIndexName>();
|
||||
auto str = ctx.callSite->args.data[0]->as<Luau::AstExprConstantString>();
|
||||
if (!index || !str)
|
||||
return {};
|
||||
|
||||
std::optional<DefId> def = ctx.dfg->getDef(index->expr);
|
||||
if (!def)
|
||||
return {};
|
||||
|
||||
std::optional<TypeFun> tfun = ctx.scope->lookupType(std::string(str->value.data, str->value.size));
|
||||
if (!tfun)
|
||||
return {};
|
||||
|
||||
return {ctx.connectiveArena->proposition(*def, tfun->type)};
|
||||
}
|
||||
|
||||
struct RefinementClassFixture : BuiltinsFixture
|
||||
{
|
||||
RefinementClassFixture()
|
||||
|
@ -56,6 +78,7 @@ struct RefinementClassFixture : BuiltinsFixture
|
|||
TypePackId isARets = arena.addTypePack({typeChecker.booleanType});
|
||||
TypeId isA = arena.addType(FunctionTypeVar{isAParams, isARets});
|
||||
getMutable<FunctionTypeVar>(isA)->magicFunction = magicFunctionInstanceIsA;
|
||||
getMutable<FunctionTypeVar>(isA)->dcrMagicRefinement = dcrMagicRefinementInstanceIsA;
|
||||
|
||||
getMutable<ClassTypeVar>(inst)->props = {
|
||||
{"Name", Property{typeChecker.stringType}},
|
||||
|
@ -397,13 +420,21 @@ TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties")
|
|||
local t: {x: number?} = {x = 1}
|
||||
|
||||
if t.x then
|
||||
local foo: number = t.x
|
||||
local t2 = t
|
||||
local foo = t.x
|
||||
end
|
||||
|
||||
local bar = t.x
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK("{| x: number? |} & {| x: ~(false?) |}" == toString(requireTypeAtPosition({4, 23})));
|
||||
CHECK("(number?) & ~(false?)" == toString(requireTypeAtPosition({5, 26})));
|
||||
}
|
||||
|
||||
CHECK_EQ("number?", toString(requireType("bar")));
|
||||
}
|
||||
|
||||
|
@ -442,12 +473,24 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(R"(Type '{| x: number? |}' could not be converted into '{| x: number |}'
|
||||
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
{
|
||||
CHECK_EQ(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)",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(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')",
|
||||
toString(result.errors[0]));
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -464,8 +507,8 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_another_lvalue")
|
|||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "((number | string)?) & (boolean?)"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "((number | string)?) & (boolean?)"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "((number | string)?) & unknown"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "(boolean?) & unknown"); // a == b
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 33})), "((number | string)?) & unknown"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 36})), "(boolean?) & unknown"); // a ~= b
|
||||
|
@ -496,7 +539,7 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_equal_to_a_term")
|
|||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & number"); // a == 1
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & unknown"); // a == 1
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "((number | string)?) & unknown"); // a ~= 1
|
||||
}
|
||||
else
|
||||
|
@ -548,8 +591,8 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
|
|||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & ~nil"); // a ~= nil
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "((number | string)?) & nil"); // a == nil
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "((number | string)?) & ~nil"); // a ~= nil
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "((number | string)?) & unknown"); // a == nil
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -573,8 +616,8 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
|||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33}), opts), "(string?) & a"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36}), opts), "(string?) & a"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33}), opts), "a & unknown"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36}), opts), "(string?) & unknown"); // a == b
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -628,8 +671,8 @@ TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil")
|
|||
CHECK_EQ(toString(requireTypeAtPosition({6, 29})), "string & unknown"); // a ~= b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({6, 32})), "(string?) & unknown"); // a ~= b
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "(string?) & string"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "(string?) & string"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 29})), "string & unknown"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({8, 32})), "(string?) & unknown"); // a == b
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1146,8 +1189,17 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({| tag: "exists", x: string |} | {| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"(({| tag: "exists", x: string |} | {| tag: "missing", x: nil |}) & {| x: ~(false?) |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(
|
||||
R"(({| tag: "exists", x: string |} | {| tag: "missing", x: nil |}) & {| x: ~~(false?) |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({| tag: "exists", x: string |} | {| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
|
||||
|
@ -1159,17 +1211,57 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_tag")
|
|||
|
||||
local function f(animal: Animal)
|
||||
if animal.tag == "Cat" then
|
||||
local cat: Cat = animal
|
||||
local cat = animal
|
||||
elseif animal.tag == "Dog" then
|
||||
local dog: Dog = animal
|
||||
local dog = animal
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("Cat", toString(requireTypeAtPosition({7, 33})));
|
||||
CHECK_EQ("Dog", toString(requireTypeAtPosition({9, 33})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
|
||||
CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |} & {| tag: "Dog" |})", toString(requireTypeAtPosition({9, 33})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Cat", toString(requireTypeAtPosition({7, 33})));
|
||||
CHECK_EQ("Dog", toString(requireTypeAtPosition({9, 33})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "discriminate_tag_with_implicit_else")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauImplicitElseRefinement", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Cat = {tag: "Cat", name: string, catfood: string}
|
||||
type Dog = {tag: "Dog", name: string, dogfood: string}
|
||||
type Animal = Cat | Dog
|
||||
|
||||
local function f(animal: Animal)
|
||||
if animal.tag == "Cat" then
|
||||
local cat = animal
|
||||
else
|
||||
local dog = animal
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"((Cat | Dog) & {| tag: "Cat" |})", toString(requireTypeAtPosition({7, 33})));
|
||||
CHECK_EQ(R"((Cat | Dog) & {| tag: ~"Cat" |})", toString(requireTypeAtPosition({9, 33})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Cat", toString(requireTypeAtPosition({7, 33})));
|
||||
CHECK_EQ("Dog", toString(requireTypeAtPosition({9, 33})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "and_or_peephole_refinement")
|
||||
|
@ -1258,8 +1350,16 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x")
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ(R"(({| tag: "Folder", x: Folder |} | {| tag: "Part", x: Part |}) & {| x: Part |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"(({| tag: "Folder", x: Folder |} | {| tag: "Part", x: Part |}) & {| x: ~Part |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector")
|
||||
|
@ -1399,8 +1499,96 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is
|
|||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("Folder", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ("any", toString(requireTypeAtPosition({7, 28})));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("Folder & Instance & {- -}", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ("(~Folder | ~Instance) & {- -} & never", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Folder", toString(requireTypeAtPosition({5, 28})));
|
||||
CHECK_EQ("any", toString(requireTypeAtPosition({7, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_instance_without_using_typeof")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: Instance)
|
||||
if x:IsA("Folder") then
|
||||
local foo = x
|
||||
elseif typeof(x) == "table" then
|
||||
local foo = x
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("Folder & Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance & ~Folder & never", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("never", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_without_using_typeof")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: Part | Folder)
|
||||
if x:IsA("Folder") then
|
||||
local foo = x
|
||||
else
|
||||
local foo = x
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("(Folder | Part) & Folder", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("(Folder | Part) & ~Folder", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Folder", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Part", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahead_of_time")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x): Instance
|
||||
if x:IsA("Folder") then
|
||||
local foo = x
|
||||
else
|
||||
local foo = x
|
||||
end
|
||||
|
||||
return x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({3, 28})));
|
||||
CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RefinementClassFixture, "x_is_not_instance_or_else_not_part")
|
||||
|
@ -1526,4 +1714,18 @@ TEST_CASE_FIXTURE(Fixture, "else_with_no_explicit_expression_should_also_refine_
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_filtered_refined_types_are_followed")
|
||||
{
|
||||
ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local _
|
||||
do
|
||||
local _ = _ ~= _ or _ or _
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
|||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauNoMoreGlobalSingletonTypes)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -2024,7 +2025,12 @@ local b: B = a
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'y' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'y' is not compatible. Type 'number' could not be converted into 'string')");
|
||||
}
|
||||
|
@ -2043,7 +2049,14 @@ local b: B = a
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'b' is not compatible. Type 'AS' could not be converted into 'BS'
|
||||
caused by:
|
||||
Property 'y' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'b' is not compatible. Type 'AS' could not be converted into 'BS'
|
||||
caused by:
|
||||
|
@ -2063,7 +2076,14 @@ local c2: typeof(a2) = b2
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'b1' could not be converted into 'a1'
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
CHECK_EQ(toString(result.errors[0]), 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)");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), 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:
|
||||
|
@ -2098,7 +2118,12 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property '[indexer key]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property '[indexer key]' is not compatible. Type 'number' could not be converted into 'string')");
|
||||
}
|
||||
|
@ -2114,7 +2139,12 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
if (FFlag::LuauTypeMismatchInvarianceInError)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property '[indexer value]' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property '[indexer value]' is not compatible. Type 'number' could not be converted into 'string')");
|
||||
}
|
||||
|
@ -3261,7 +3291,7 @@ caused by:
|
|||
TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compatible")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner", true}; // Changes argument from table type to primitive
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true}; // Changes argument from table type to primitive
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s): string
|
||||
|
@ -3308,7 +3338,7 @@ caused by:
|
|||
TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeSubtyping{"LuauScalarShapeSubtyping", true};
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner", true};
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function stringByteList(str)
|
||||
|
@ -3394,7 +3424,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_has_a_side_effect")
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("foo")) == "{ @metatable { __add: (a, b) -> number }, { } }");
|
||||
CHECK(toString(requireType("foo")) == "{ @metatable { __add: <a, b>(a, b) -> number }, { } }");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tables_should_be_fully_populated")
|
||||
|
@ -3413,4 +3443,43 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tables_should_be_fully_populated")
|
|||
CHECK_EQ("{ x: *error-type*, y: number }", toString(requireType("t"), opts));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_table_indexer_unification_can_bound_owner_to_string")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
sin,_ = nil
|
||||
_ = _[_.sin][_._][_][_]._
|
||||
_[_] = _
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_table_extra_prop_unification_can_bound_owner_to_string")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
l0,_ = nil
|
||||
_ = _,_[_.n5]._[_][_][_]._
|
||||
_._.foreach[_],_ = _[_],_._
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_typelevel_promote_on_changed_table_type")
|
||||
{
|
||||
ScopedFastFlag luauScalarShapeUnifyToMtOwner{"LuauScalarShapeUnifyToMtOwner2", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
_._,_ = nil
|
||||
_ = _.foreach[_]._,_[_.n5]._[_.foreach][_][_]._
|
||||
_ = _._
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -351,7 +351,7 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit")
|
|||
CheckResult result = check(R"(("foo"))" + rep(":lower()", limit));
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(nullptr != get<CodeTooComplex>(result.errors[0]));
|
||||
CHECK_MESSAGE(nullptr != get<CodeTooComplex>(result.errors[0]), "Expected CodeTooComplex but got " << toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "globals")
|
||||
|
@ -1159,4 +1159,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_retu
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_free_table_type_change_during_index_check")
|
||||
{
|
||||
ScopedFastFlag luauFollowInLvalueIndexCheck{"LuauFollowInLvalueIndexCheck", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local _ = nil
|
||||
while _["" >= _] do
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -110,6 +110,68 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
|
|||
CHECK_NE(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never")
|
||||
{
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(arg : string & number) : never
|
||||
return arg
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything")
|
||||
{
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(arg : string & number) : boolean
|
||||
return arg
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never")
|
||||
{
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
{"LuauUninhabitedSubAnything", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(arg : { prop : string & number }) : never
|
||||
return arg
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_anything")
|
||||
{
|
||||
ScopedFastFlag sffs[]{
|
||||
{"LuauSubtypeNormalizer", true},
|
||||
{"LuauTypeNormalization2", true},
|
||||
{"LuauUninhabitedSubAnything", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(arg : { prop : string & number }) : boolean
|
||||
return arg
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "members_of_failed_typepack_unification_are_unified_with_errorType")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -299,4 +361,19 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table
|
|||
CHECK_EQ(toString(state.errors[0]), expected);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue")
|
||||
{
|
||||
ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true};
|
||||
|
||||
TypePackVar variadicAny{VariadicTypePack{typeChecker.anyType}};
|
||||
TypePackVar packTmp{TypePack{{typeChecker.anyType}, &variadicAny}};
|
||||
TypePackVar packSub{TypePack{{typeChecker.anyType, typeChecker.anyType}, &packTmp}};
|
||||
|
||||
TypeVar freeTy{FreeTypeVar{TypeLevel{}}};
|
||||
TypePackVar freeTp{FreeTypePack{TypeLevel{}}};
|
||||
TypePackVar packSuper{TypePack{{&freeTy}, &freeTp}};
|
||||
|
||||
state.tryUnify(&packSub, &packSuper);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauFunctionReturnStringificationFixup);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("TypePackTests");
|
||||
|
@ -311,10 +309,7 @@ local c: Packed<string, number, boolean>
|
|||
auto ttvA = get<TableTypeVar>(requireType("a"));
|
||||
REQUIRE(ttvA);
|
||||
CHECK_EQ(toString(requireType("a")), "Packed<number>");
|
||||
if (FFlag::LuauFunctionReturnStringificationFixup)
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> number |}");
|
||||
else
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> (number) |}");
|
||||
CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> number |}");
|
||||
REQUIRE(ttvA->instantiatedTypeParams.size() == 1);
|
||||
REQUIRE(ttvA->instantiatedTypePackParams.size() == 1);
|
||||
CHECK_EQ(toString(ttvA->instantiatedTypeParams[0], {true}), "number");
|
||||
|
@ -467,8 +462,6 @@ type I<S..., R...> = W<number, (string, S...), R...>
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit")
|
||||
{
|
||||
ScopedFastFlag sff("LuauFunctionReturnStringificationFixup", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type X<T...> = (T...) -> (T...)
|
||||
|
||||
|
@ -492,8 +485,6 @@ type F = X<(string, ...number)>
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit_multi")
|
||||
{
|
||||
ScopedFastFlag sff("LuauFunctionReturnStringificationFixup", true);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Y<T..., U...> = (T...) -> (U...)
|
||||
|
||||
|
@ -1002,6 +993,10 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_packs_with_tails_in_vararg_adjustment")
|
||||
{
|
||||
std::optional<ScopedFastFlag> sff;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
sff = {"LuauInstantiateInSubtyping", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function wrapReject<TArg, TResult>(fn: (self: any, ...TArg) -> ...TResult): (self: any, ...TArg) -> ...TResult
|
||||
return function(self, ...)
|
||||
|
@ -1017,4 +1012,46 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_packs_with_tails_in_vararg_adjustment")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generalize_expectedTypes_with_proper_scope")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{"DebugLuauDeferredConstraintResolution", true},
|
||||
{"LuauInstantiateInSubtyping", true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f<TResult>(fn: () -> ...TResult): () -> ...TResult
|
||||
return function()
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_typepack_iter_follow")
|
||||
{
|
||||
ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local _
|
||||
local _ = _,_(),_(_)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_typepack_iter_follow_2")
|
||||
{
|
||||
ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function test(name, searchTerm)
|
||||
local found = string.find(name:lower(), searchTerm:lower())
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -729,4 +729,37 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics
|
|||
"of the union options are compatible");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(t): { x: number } | { x: string }
|
||||
local x = t.x
|
||||
return t
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("({| x: number |} | {| x: string |}) -> {| x: number |} | {| x: string |}", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(t: { x: number } | { x: string })
|
||||
return t.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("({| x: number |} | {| x: string |}) -> number | string", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -22,14 +22,7 @@ TEST_CASE_FIXTURE(Fixture, "throw_when_limit_is_exceeded")
|
|||
|
||||
TypeId tType = requireType("t");
|
||||
|
||||
if (FFlag::LuauIceExceptionInheritanceChange)
|
||||
{
|
||||
CHECK_THROWS_AS(toString(tType), RecursionLimitException);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_THROWS_AS(toString(tType), RecursionLimitException_DEPRECATED);
|
||||
}
|
||||
CHECK_THROWS_AS(toString(tType), RecursionLimitException);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_throw_when_limit_is_high_enough")
|
||||
|
|
|
@ -14,9 +14,11 @@ AstQuery::getDocumentationSymbolAtPosition.overloaded_class_method
|
|||
AstQuery::getDocumentationSymbolAtPosition.overloaded_fn
|
||||
AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
|
||||
AutocompleteTest.autocomplete_first_function_arg_expected_type
|
||||
AutocompleteTest.autocomplete_interpolated_string
|
||||
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||
AutocompleteTest.autocomplete_interpolated_string_constant
|
||||
AutocompleteTest.autocomplete_interpolated_string_expression
|
||||
AutocompleteTest.autocomplete_interpolated_string_expression_with_comments
|
||||
AutocompleteTest.autocomplete_oop_implicit_self
|
||||
AutocompleteTest.autocomplete_string_singleton_equality
|
||||
AutocompleteTest.autocomplete_string_singleton_escape
|
||||
AutocompleteTest.autocomplete_string_singletons
|
||||
AutocompleteTest.autocompleteProp_index_function_metamethod_is_variadic
|
||||
|
@ -25,9 +27,11 @@ AutocompleteTest.do_wrong_compatible_self_calls
|
|||
AutocompleteTest.keyword_methods
|
||||
AutocompleteTest.no_incompatible_self_calls
|
||||
AutocompleteTest.no_wrong_compatible_self_calls_with_generics
|
||||
AutocompleteTest.suggest_external_module_type
|
||||
AutocompleteTest.suggest_table_keys
|
||||
AutocompleteTest.type_correct_argument_type_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
|
||||
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
|
||||
|
@ -68,7 +72,6 @@ BuiltinTests.select_with_decimal_argument_is_rounded_down
|
|||
BuiltinTests.set_metatable_needs_arguments
|
||||
BuiltinTests.setmetatable_should_not_mutate_persisted_types
|
||||
BuiltinTests.sort_with_bad_predicate
|
||||
BuiltinTests.string_format_arg_count_mismatch
|
||||
BuiltinTests.string_format_as_method
|
||||
BuiltinTests.string_format_correctly_ordered_types
|
||||
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
|
||||
|
@ -106,10 +109,10 @@ GenericsTests.generic_factories
|
|||
GenericsTests.generic_functions_should_be_memory_safe
|
||||
GenericsTests.generic_table_method
|
||||
GenericsTests.generic_type_pack_parentheses
|
||||
GenericsTests.generic_type_pack_unification2
|
||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||
GenericsTests.infer_generic_function_function_argument
|
||||
GenericsTests.infer_generic_function_function_argument_overloaded
|
||||
GenericsTests.infer_generic_lib_function_function_argument
|
||||
GenericsTests.infer_generic_methods
|
||||
GenericsTests.infer_generic_property
|
||||
GenericsTests.instantiated_function_argument_names
|
||||
|
@ -117,9 +120,6 @@ GenericsTests.instantiation_sharing_types
|
|||
GenericsTests.no_stack_overflow_from_quantifying
|
||||
GenericsTests.reject_clashing_generic_and_pack_names
|
||||
GenericsTests.self_recursive_instantiated_param
|
||||
IntersectionTypes.index_on_an_intersection_type_with_mixed_types
|
||||
IntersectionTypes.index_on_an_intersection_type_with_property_guaranteed_to_exist
|
||||
IntersectionTypes.index_on_an_intersection_type_works_at_arbitrary_depth
|
||||
IntersectionTypes.no_stack_overflow_from_flattenintersection
|
||||
IntersectionTypes.select_correct_union_fn
|
||||
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
|
||||
|
@ -151,6 +151,7 @@ ProvisionalTests.bail_early_if_unification_is_too_complicated
|
|||
ProvisionalTests.discriminate_from_x_not_equal_to_nil
|
||||
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
||||
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
||||
ProvisionalTests.free_options_cannot_be_unified_together
|
||||
ProvisionalTests.generic_type_leak_to_module_interface_variadic
|
||||
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
|
||||
ProvisionalTests.lvalue_equals_another_lvalue_with_no_overlap
|
||||
|
@ -164,15 +165,15 @@ ProvisionalTests.while_body_are_also_refined
|
|||
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
|
||||
RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number
|
||||
RefinementTest.assert_non_binary_expressions_actually_resolve_constraints
|
||||
RefinementTest.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
||||
RefinementTest.call_an_incompatible_function_after_using_typeguard
|
||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined
|
||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2
|
||||
RefinementTest.discriminate_from_isa_of_x
|
||||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false
|
||||
RefinementTest.discriminate_tag
|
||||
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
|
||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
||||
RefinementTest.fuzz_filtered_refined_types_are_followed
|
||||
RefinementTest.index_on_a_refined_property
|
||||
RefinementTest.invert_is_truthy_constraint_ifelse_expression
|
||||
RefinementTest.is_truthy_constraint_ifelse_expression
|
||||
|
@ -181,7 +182,6 @@ RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
|||
RefinementTest.not_t_or_some_prop_of_t
|
||||
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table
|
||||
RefinementTest.refine_unknowns
|
||||
RefinementTest.truthy_constraint_on_properties
|
||||
RefinementTest.type_comparison_ifelse_expression
|
||||
RefinementTest.type_guard_can_filter_for_intersection_of_tables
|
||||
RefinementTest.type_guard_narrowed_into_nothingness
|
||||
|
@ -210,7 +210,6 @@ TableTests.defining_a_self_method_for_a_builtin_sealed_table_must_fail
|
|||
TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail
|
||||
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
||||
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
||||
TableTests.dont_leak_free_table_props
|
||||
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
||||
TableTests.dont_suggest_exact_match_keys
|
||||
TableTests.error_detailed_metatable_prop
|
||||
|
@ -240,6 +239,7 @@ TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_i
|
|||
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
|
||||
TableTests.leaking_bad_metatable_errors
|
||||
TableTests.less_exponential_blowup_please
|
||||
TableTests.meta_add
|
||||
TableTests.meta_add_both_ways
|
||||
TableTests.meta_add_inferred
|
||||
TableTests.metatable_mismatch_should_fail
|
||||
|
@ -252,8 +252,6 @@ TableTests.only_ascribe_synthetic_names_at_module_scope
|
|||
TableTests.oop_indexer_works
|
||||
TableTests.oop_polymorphic
|
||||
TableTests.open_table_unification_2
|
||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
|
||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
|
||||
TableTests.persistent_sealed_table_is_immutable
|
||||
TableTests.prop_access_on_key_whose_types_mismatches
|
||||
TableTests.property_lookup_through_tabletypevar_metatable
|
||||
|
@ -268,6 +266,7 @@ TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
|
|||
TableTests.shared_selfs
|
||||
TableTests.shared_selfs_from_free_param
|
||||
TableTests.shared_selfs_through_metatables
|
||||
TableTests.table_call_metamethod_basic
|
||||
TableTests.table_indexing_error_location
|
||||
TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict
|
||||
TableTests.table_insert_should_cope_with_optional_properties_in_strict
|
||||
|
@ -323,6 +322,7 @@ TypeInfer.checking_should_not_ice
|
|||
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
||||
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
||||
TypeInfer.fuzz_free_table_type_change_during_index_check
|
||||
TypeInfer.globals
|
||||
TypeInfer.globals2
|
||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||
|
@ -335,7 +335,6 @@ TypeInfer.tc_interpolated_string_with_invalid_expression
|
|||
TypeInfer.type_infer_recursion_limit_no_ice
|
||||
TypeInfer.type_infer_recursion_limit_normalizer
|
||||
TypeInferAnyError.for_in_loop_iterator_is_any2
|
||||
TypeInferAnyError.for_in_loop_iterator_is_error2
|
||||
TypeInferClasses.can_read_prop_of_base_class_using_string
|
||||
TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added
|
||||
|
@ -351,8 +350,6 @@ TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
|||
TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
|
||||
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
|
||||
TypeInferFunctions.duplicate_functions_with_different_signatures_not_allowed_in_nonstrict
|
||||
TypeInferFunctions.free_is_not_bound_to_unknown
|
||||
TypeInferFunctions.func_expr_doesnt_leak_free
|
||||
TypeInferFunctions.function_cast_error_uses_correct_language
|
||||
TypeInferFunctions.function_decl_non_self_sealed_overwrite_2
|
||||
TypeInferFunctions.function_decl_non_self_unsealed_overwrite
|
||||
|
@ -385,12 +382,11 @@ TypeInferLoops.for_in_loop
|
|||
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
||||
TypeInferLoops.for_in_loop_with_next
|
||||
TypeInferLoops.for_in_with_generic_next
|
||||
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
||||
TypeInferLoops.loop_iter_metamethod_ok_with_inference
|
||||
TypeInferLoops.loop_iter_no_indexer_nonstrict
|
||||
TypeInferLoops.loop_iter_trailing_nil
|
||||
TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||
TypeInferModules.custom_require_global
|
||||
TypeInferModules.do_not_modify_imported_types
|
||||
TypeInferModules.module_type_conflict
|
||||
|
@ -414,6 +410,8 @@ TypeInferOperators.compound_assign_mismatch_result
|
|||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||
TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators
|
||||
TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown
|
||||
TypeInferOperators.mm_comparisons_must_return_a_boolean
|
||||
TypeInferOperators.mm_ops_must_return_a_value
|
||||
TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not
|
||||
TypeInferOperators.refine_and_or
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
||||
|
@ -427,16 +425,13 @@ TypeInferUnknownNever.assign_to_prop_which_is_never
|
|||
TypeInferUnknownNever.assign_to_subscript_which_is_never
|
||||
TypeInferUnknownNever.call_never
|
||||
TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators
|
||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
|
||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
||||
TypeInferUnknownNever.math_operators_and_never
|
||||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
|
||||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2
|
||||
TypeInferUnknownNever.unary_minus_of_never
|
||||
TypePackTests.detect_cyclic_typepacks2
|
||||
TypePackTests.higher_order_function
|
||||
TypePackTests.pack_tail_unification_check
|
||||
TypePackTests.parenthesized_varargs_returns_any
|
||||
TypePackTests.type_alias_backwards_compatible
|
||||
TypePackTests.type_alias_default_export
|
||||
TypePackTests.type_alias_default_mixed_self
|
||||
|
@ -456,7 +451,6 @@ TypePackTests.type_alias_type_packs_nested
|
|||
TypePackTests.type_pack_type_parameters
|
||||
TypePackTests.unify_variadic_tails_in_arguments
|
||||
TypePackTests.unify_variadic_tails_in_arguments_free
|
||||
TypePackTests.varargs_inference_through_multiple_scopes
|
||||
TypePackTests.variadic_packs
|
||||
TypeSingletons.error_detailed_tagged_union_mismatch_bool
|
||||
TypeSingletons.error_detailed_tagged_union_mismatch_string
|
||||
|
@ -477,11 +471,8 @@ TypeSingletons.widening_happens_almost_everywhere_except_for_tables
|
|||
UnionTypes.error_detailed_optional
|
||||
UnionTypes.error_detailed_union_all
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.index_on_a_union_type_with_mixed_types
|
||||
UnionTypes.index_on_a_union_type_with_one_optional_property
|
||||
UnionTypes.index_on_a_union_type_with_one_property_of_type_any
|
||||
UnionTypes.index_on_a_union_type_with_property_guaranteed_to_exist
|
||||
UnionTypes.index_on_a_union_type_works_at_arbitrary_depth
|
||||
UnionTypes.optional_assignment_errors
|
||||
UnionTypes.optional_call_error
|
||||
UnionTypes.optional_field_access_error
|
||||
|
|
Loading…
Reference in a new issue