mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/595 (#1044)
* Rerun clang-format on the code * Fix the variance on indexer result subtyping. This fixes some issues with inconsistent error reporting. * Fix a bug in the normalization logic for intersections of strings New Type Solver * New overload selection logic * Subtype tests now correctly treat a generic as its upper bound within that generic's scope * Semantic subtyping for negation types * Semantic subtyping between strings and compatible table types like `{lower: (string) -> string}` * Further work toward finalizing our new subtype test * Correctly generalize module-scope symbols Native Codegen * Lowering statistics for assembly * Make executable allocation size/limit configurable without a rebuild. Use `FInt::LuauCodeGenBlockSize` and `FInt::LuauCodeGenMaxTotalSize`. --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com>
This commit is contained in:
parent
a35d3d4588
commit
31a017c5c7
@ -101,9 +101,10 @@ struct ConstraintGraphBuilder
|
|||||||
|
|
||||||
DcrLogger* logger;
|
DcrLogger* logger;
|
||||||
|
|
||||||
ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver, NotNull<BuiltinTypes> builtinTypes,
|
ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver,
|
||||||
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
|
||||||
DcrLogger* logger, NotNull<DataFlowGraph> dfg, std::vector<RequireCycle> requireCycles);
|
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, DcrLogger* logger, NotNull<DataFlowGraph> dfg,
|
||||||
|
std::vector<RequireCycle> requireCycles);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabricates a new free type belonging to a given scope.
|
* Fabricates a new free type belonging to a given scope.
|
||||||
|
@ -23,4 +23,4 @@ struct GlobalTypes
|
|||||||
ScopePtr globalScope; // shared by all modules
|
ScopePtr globalScope; // shared by all modules
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace Luau
|
||||||
|
@ -17,8 +17,8 @@ struct TypeCheckLimits;
|
|||||||
// A substitution which replaces generic types in a given set by free types.
|
// A substitution which replaces generic types in a given set by free types.
|
||||||
struct ReplaceGenerics : Substitution
|
struct ReplaceGenerics : Substitution
|
||||||
{
|
{
|
||||||
ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope, const std::vector<TypeId>& generics,
|
ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope,
|
||||||
const std::vector<TypePackId>& genericPacks)
|
const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
|
||||||
: Substitution(log, arena)
|
: Substitution(log, arena)
|
||||||
, builtinTypes(builtinTypes)
|
, builtinTypes(builtinTypes)
|
||||||
, level(level)
|
, level(level)
|
||||||
@ -77,6 +77,7 @@ struct Instantiation : Substitution
|
|||||||
* Instantiation fails only when processing the type causes internal recursion
|
* Instantiation fails only when processing the type causes internal recursion
|
||||||
* limits to be exceeded.
|
* limits to be exceeded.
|
||||||
*/
|
*/
|
||||||
std::optional<TypeId> instantiate(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty);
|
std::optional<TypeId> instantiate(
|
||||||
|
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -19,20 +19,18 @@ class TypeIds;
|
|||||||
class Normalizer;
|
class Normalizer;
|
||||||
struct NormalizedType;
|
struct NormalizedType;
|
||||||
struct NormalizedClassType;
|
struct NormalizedClassType;
|
||||||
|
struct NormalizedStringType;
|
||||||
struct NormalizedFunctionType;
|
struct NormalizedFunctionType;
|
||||||
|
|
||||||
|
|
||||||
struct SubtypingResult
|
struct SubtypingResult
|
||||||
{
|
{
|
||||||
// Did the test succeed?
|
|
||||||
bool isSubtype = false;
|
bool isSubtype = false;
|
||||||
bool isErrorSuppressing = false;
|
bool isErrorSuppressing = false;
|
||||||
bool normalizationTooComplex = false;
|
bool normalizationTooComplex = false;
|
||||||
|
|
||||||
// If so, what constraints are implied by this relation?
|
SubtypingResult& andAlso(const SubtypingResult& other);
|
||||||
// If not, what happened?
|
SubtypingResult& orElse(const SubtypingResult& other);
|
||||||
|
|
||||||
void andAlso(const SubtypingResult& other);
|
|
||||||
void orElse(const SubtypingResult& other);
|
|
||||||
|
|
||||||
// Only negates the `isSubtype`.
|
// Only negates the `isSubtype`.
|
||||||
static SubtypingResult negate(const SubtypingResult& result);
|
static SubtypingResult negate(const SubtypingResult& result);
|
||||||
@ -47,6 +45,8 @@ struct Subtyping
|
|||||||
NotNull<Normalizer> normalizer;
|
NotNull<Normalizer> normalizer;
|
||||||
NotNull<InternalErrorReporter> iceReporter;
|
NotNull<InternalErrorReporter> iceReporter;
|
||||||
|
|
||||||
|
NotNull<Scope> scope;
|
||||||
|
|
||||||
enum class Variance
|
enum class Variance
|
||||||
{
|
{
|
||||||
Covariant,
|
Covariant,
|
||||||
@ -72,6 +72,12 @@ struct Subtyping
|
|||||||
|
|
||||||
SeenSet seenTypes;
|
SeenSet seenTypes;
|
||||||
|
|
||||||
|
Subtyping(const Subtyping&) = delete;
|
||||||
|
Subtyping& operator=(const Subtyping&) = delete;
|
||||||
|
|
||||||
|
Subtyping(Subtyping&&) = default;
|
||||||
|
Subtyping& operator=(Subtyping&&) = default;
|
||||||
|
|
||||||
// TODO cache
|
// TODO cache
|
||||||
// TODO cyclic types
|
// TODO cyclic types
|
||||||
// TODO recursion limits
|
// TODO recursion limits
|
||||||
@ -80,43 +86,61 @@ struct Subtyping
|
|||||||
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy);
|
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SubtypingResult isSubtype_(TypeId subTy, TypeId superTy);
|
SubtypingResult isCovariantWith(TypeId subTy, TypeId superTy);
|
||||||
SubtypingResult isSubtype_(TypePackId subTy, TypePackId superTy);
|
SubtypingResult isCovariantWith(TypePackId subTy, TypePackId superTy);
|
||||||
|
|
||||||
template<typename SubTy, typename SuperTy>
|
template<typename SubTy, typename SuperTy>
|
||||||
SubtypingResult isSubtype_(const TryPair<const SubTy*, const SuperTy*>& pair);
|
SubtypingResult isContravariantWith(SubTy&& subTy, SuperTy&& superTy);
|
||||||
|
|
||||||
SubtypingResult isSubtype_(TypeId subTy, const UnionType* superUnion);
|
template<typename SubTy, typename SuperTy>
|
||||||
SubtypingResult isSubtype_(const UnionType* subUnion, TypeId superTy);
|
SubtypingResult isInvariantWith(SubTy&& subTy, SuperTy&& superTy);
|
||||||
SubtypingResult isSubtype_(TypeId subTy, const IntersectionType* superIntersection);
|
|
||||||
SubtypingResult isSubtype_(const IntersectionType* subIntersection, TypeId superTy);
|
|
||||||
SubtypingResult isSubtype_(const PrimitiveType* subPrim, const PrimitiveType* superPrim);
|
|
||||||
SubtypingResult isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim);
|
|
||||||
SubtypingResult isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton);
|
|
||||||
SubtypingResult isSubtype_(const TableType* subTable, const TableType* superTable);
|
|
||||||
SubtypingResult isSubtype_(const MetatableType* subMt, const MetatableType* superMt);
|
|
||||||
SubtypingResult isSubtype_(const MetatableType* subMt, const TableType* superTable);
|
|
||||||
SubtypingResult isSubtype_(const ClassType* subClass, const ClassType* superClass);
|
|
||||||
SubtypingResult isSubtype_(const ClassType* subClass, const TableType* superTable); // Actually a class <: shape.
|
|
||||||
SubtypingResult isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction);
|
|
||||||
SubtypingResult isSubtype_(const PrimitiveType* subPrim, const TableType* superTable);
|
|
||||||
SubtypingResult isSubtype_(const SingletonType* subSingleton, const TableType* superTable);
|
|
||||||
|
|
||||||
SubtypingResult isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm);
|
template<typename SubTy, typename SuperTy>
|
||||||
SubtypingResult isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables);
|
SubtypingResult isCovariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||||
SubtypingResult isSubtype_(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction);
|
|
||||||
SubtypingResult isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes);
|
|
||||||
|
|
||||||
SubtypingResult isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
|
template<typename SubTy, typename SuperTy>
|
||||||
|
SubtypingResult isContravariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||||
|
|
||||||
|
template<typename SubTy, typename SuperTy>
|
||||||
|
SubtypingResult isInvariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||||
|
|
||||||
|
SubtypingResult isCovariantWith(TypeId subTy, const UnionType* superUnion);
|
||||||
|
SubtypingResult isCovariantWith(const UnionType* subUnion, TypeId superTy);
|
||||||
|
SubtypingResult isCovariantWith(TypeId subTy, const IntersectionType* superIntersection);
|
||||||
|
SubtypingResult isCovariantWith(const IntersectionType* subIntersection, TypeId superTy);
|
||||||
|
|
||||||
|
SubtypingResult isCovariantWith(const NegationType* subNegation, TypeId superTy);
|
||||||
|
SubtypingResult isCovariantWith(const TypeId subTy, const NegationType* superNegation);
|
||||||
|
|
||||||
|
SubtypingResult isCovariantWith(const PrimitiveType* subPrim, const PrimitiveType* superPrim);
|
||||||
|
SubtypingResult isCovariantWith(const SingletonType* subSingleton, const PrimitiveType* superPrim);
|
||||||
|
SubtypingResult isCovariantWith(const SingletonType* subSingleton, const SingletonType* superSingleton);
|
||||||
|
SubtypingResult isCovariantWith(const TableType* subTable, const TableType* superTable);
|
||||||
|
SubtypingResult isCovariantWith(const MetatableType* subMt, const MetatableType* superMt);
|
||||||
|
SubtypingResult isCovariantWith(const MetatableType* subMt, const TableType* superTable);
|
||||||
|
SubtypingResult isCovariantWith(const ClassType* subClass, const ClassType* superClass);
|
||||||
|
SubtypingResult isCovariantWith(const ClassType* subClass, const TableType* superTable);
|
||||||
|
SubtypingResult isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction);
|
||||||
|
SubtypingResult isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable);
|
||||||
|
SubtypingResult isCovariantWith(const SingletonType* subSingleton, const TableType* superTable);
|
||||||
|
|
||||||
|
SubtypingResult isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm);
|
||||||
|
SubtypingResult isCovariantWith(const NormalizedClassType& subClass, const NormalizedClassType& superClass);
|
||||||
|
SubtypingResult isCovariantWith(const NormalizedClassType& subClass, const TypeIds& superTables);
|
||||||
|
SubtypingResult isCovariantWith(const NormalizedStringType& subString, const NormalizedStringType& superString);
|
||||||
|
SubtypingResult isCovariantWith(const NormalizedStringType& subString, const TypeIds& superTables);
|
||||||
|
SubtypingResult isCovariantWith(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction);
|
||||||
|
SubtypingResult isCovariantWith(const TypeIds& subTypes, const TypeIds& superTypes);
|
||||||
|
|
||||||
|
SubtypingResult isCovariantWith(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
|
||||||
|
|
||||||
bool bindGeneric(TypeId subTp, TypeId superTp);
|
bool bindGeneric(TypeId subTp, TypeId superTp);
|
||||||
bool bindGeneric(TypePackId subTp, TypePackId superTp);
|
bool bindGeneric(TypePackId subTp, TypePackId superTp);
|
||||||
|
|
||||||
template <typename T, typename Container>
|
template<typename T, typename Container>
|
||||||
TypeId makeAggregateType(const Container& container, TypeId orElse);
|
TypeId makeAggregateType(const Container& container, TypeId orElse);
|
||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]] void unexpected(TypePackId tp);
|
||||||
void unexpected(TypePackId tp);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -849,6 +849,18 @@ bool isSubclass(const ClassType* cls, const ClassType* parent);
|
|||||||
|
|
||||||
Type* asMutable(TypeId ty);
|
Type* asMutable(TypeId ty);
|
||||||
|
|
||||||
|
template<typename... Ts, typename T>
|
||||||
|
bool is(T&& tv)
|
||||||
|
{
|
||||||
|
if (!tv)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<TypeId, T> && !(std::is_same_v<BoundType, Ts> || ...))
|
||||||
|
LUAU_ASSERT(get_if<BoundType>(&tv->ty) == nullptr);
|
||||||
|
|
||||||
|
return (get<Ts>(tv) || ...);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
const T* get(TypeId tv)
|
const T* get(TypeId tv)
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,7 @@ struct DcrLogger;
|
|||||||
struct TypeCheckLimits;
|
struct TypeCheckLimits;
|
||||||
struct UnifierSharedState;
|
struct UnifierSharedState;
|
||||||
|
|
||||||
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule& sourceModule,
|
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
|
||||||
Module* module);
|
const SourceModule& sourceModule, Module* module);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -104,7 +104,8 @@ ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypePackId
|
|||||||
// Similar to `std::optional<std::pair<A, B>>`, but whose `sizeof()` is the same as `std::pair<A, B>`
|
// Similar to `std::optional<std::pair<A, B>>`, but whose `sizeof()` is the same as `std::pair<A, B>`
|
||||||
// and cooperates with C++'s `if (auto p = ...)` syntax without the extra fatness of `std::optional`.
|
// and cooperates with C++'s `if (auto p = ...)` syntax without the extra fatness of `std::optional`.
|
||||||
template<typename A, typename B>
|
template<typename A, typename B>
|
||||||
struct TryPair {
|
struct TryPair
|
||||||
|
{
|
||||||
A first;
|
A first;
|
||||||
B second;
|
B second;
|
||||||
|
|
||||||
|
@ -105,10 +105,12 @@ struct Unifier
|
|||||||
* Populate the vector errors with any type errors that may arise.
|
* Populate the vector errors with any type errors that may arise.
|
||||||
* Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt.
|
* Populate the transaction log with the set of TypeIds that need to be reset to undo the unification attempt.
|
||||||
*/
|
*/
|
||||||
void tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
void tryUnify(
|
||||||
|
TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
void tryUnify_(
|
||||||
|
TypeId subTy, TypeId superTy, bool isFunctionCall = false, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr);
|
||||||
void tryUnifyUnionWithType(TypeId subTy, const UnionType* uv, TypeId superTy);
|
void tryUnifyUnionWithType(TypeId subTy, const UnionType* uv, TypeId superTy);
|
||||||
|
|
||||||
// Traverse the two types provided and block on any BlockedTypes we find.
|
// Traverse the two types provided and block on any BlockedTypes we find.
|
||||||
|
@ -55,8 +55,8 @@ struct Unifier2
|
|||||||
bool unify(TypePackId subTp, TypePackId superTp);
|
bool unify(TypePackId subTp, TypePackId superTp);
|
||||||
|
|
||||||
std::optional<TypeId> generalize(NotNull<Scope> scope, TypeId ty);
|
std::optional<TypeId> generalize(NotNull<Scope> scope, TypeId ty);
|
||||||
private:
|
|
||||||
|
|
||||||
|
private:
|
||||||
/**
|
/**
|
||||||
* @returns simplify(left | right)
|
* @returns simplify(left | right)
|
||||||
*/
|
*/
|
||||||
@ -72,4 +72,4 @@ private:
|
|||||||
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace Luau
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauBoundLazyTypes2)
|
LUAU_FASTFLAG(LuauBoundLazyTypes2)
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -220,7 +221,21 @@ struct GenericTypeVisitor
|
|||||||
traverse(btv->boundTo);
|
traverse(btv->boundTo);
|
||||||
}
|
}
|
||||||
else if (auto ftv = get<FreeType>(ty))
|
else if (auto ftv = get<FreeType>(ty))
|
||||||
|
{
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
{
|
||||||
|
if (visit(ty, *ftv))
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(ftv->lowerBound);
|
||||||
|
traverse(ftv->lowerBound);
|
||||||
|
|
||||||
|
LUAU_ASSERT(ftv->upperBound);
|
||||||
|
traverse(ftv->upperBound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
visit(ty, *ftv);
|
visit(ty, *ftv);
|
||||||
|
}
|
||||||
else if (auto gtv = get<GenericType>(ty))
|
else if (auto gtv = get<GenericType>(ty))
|
||||||
visit(ty, *gtv);
|
visit(ty, *gtv);
|
||||||
else if (auto etv = get<ErrorType>(ty))
|
else if (auto etv = get<ErrorType>(ty))
|
||||||
|
@ -282,20 +282,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
|||||||
ParenthesesRecommendation parens =
|
ParenthesesRecommendation parens =
|
||||||
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
|
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
|
||||||
|
|
||||||
result[name] = AutocompleteEntry{
|
result[name] = AutocompleteEntry{AutocompleteEntryKind::Property, type, prop.deprecated, isWrongIndexer(type), typeCorrect,
|
||||||
AutocompleteEntryKind::Property,
|
containingClass, &prop, prop.documentationSymbol, {}, parens, {}, indexType == PropIndexType::Colon};
|
||||||
type,
|
|
||||||
prop.deprecated,
|
|
||||||
isWrongIndexer(type),
|
|
||||||
typeCorrect,
|
|
||||||
containingClass,
|
|
||||||
&prop,
|
|
||||||
prop.documentationSymbol,
|
|
||||||
{},
|
|
||||||
parens,
|
|
||||||
{},
|
|
||||||
indexType == PropIndexType::Colon
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -606,7 +594,7 @@ std::optional<TypeId> getLocalTypeInScopeAt(const Module& module, Position posit
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template<typename T>
|
||||||
static std::optional<std::string> tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments)
|
static std::optional<std::string> tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments)
|
||||||
{
|
{
|
||||||
ToStringOptions opts;
|
ToStringOptions opts;
|
||||||
@ -1461,7 +1449,8 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<AutocompleteEntry> makeAnonymousAutofilled(const ModulePtr& module, Position position, const AstNode* node, const std::vector<AstNode*>& ancestry)
|
static std::optional<AutocompleteEntry> makeAnonymousAutofilled(
|
||||||
|
const ModulePtr& module, Position position, const AstNode* node, const std::vector<AstNode*>& ancestry)
|
||||||
{
|
{
|
||||||
const AstExprCall* call = node->as<AstExprCall>();
|
const AstExprCall* call = node->as<AstExprCall>();
|
||||||
if (!call && ancestry.size() > 1)
|
if (!call && ancestry.size() > 1)
|
||||||
|
@ -780,7 +780,7 @@ void TypeCloner::operator()(const UnionType& t)
|
|||||||
// We're just using this FreeType as a placeholder until we've finished
|
// We're just using this FreeType as a placeholder until we've finished
|
||||||
// cloning the parts of this union so it is okay that its bounds are
|
// cloning the parts of this union so it is okay that its bounds are
|
||||||
// nullptr. We'll never indirect them.
|
// nullptr. We'll never indirect them.
|
||||||
TypeId result = dest.addType(FreeType{nullptr, /*lowerBound*/nullptr, /*upperBound*/nullptr});
|
TypeId result = dest.addType(FreeType{nullptr, /*lowerBound*/ nullptr, /*upperBound*/ nullptr});
|
||||||
seenTypes[typeId] = result;
|
seenTypes[typeId] = result;
|
||||||
|
|
||||||
std::vector<TypeId> options;
|
std::vector<TypeId> options;
|
||||||
|
@ -430,6 +430,35 @@ bool ConstraintSolver::isDone()
|
|||||||
return unsolvedConstraints.empty();
|
return unsolvedConstraints.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct TypeAndLocation
|
||||||
|
{
|
||||||
|
TypeId typeId;
|
||||||
|
Location location;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FreeTypeSearcher : TypeOnceVisitor
|
||||||
|
{
|
||||||
|
std::deque<TypeAndLocation>* result;
|
||||||
|
Location location;
|
||||||
|
|
||||||
|
FreeTypeSearcher(std::deque<TypeAndLocation>* result, Location location)
|
||||||
|
: result(result)
|
||||||
|
, location(location)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const FreeType&) override
|
||||||
|
{
|
||||||
|
result->push_back({ty, location});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void ConstraintSolver::finalizeModule()
|
void ConstraintSolver::finalizeModule()
|
||||||
{
|
{
|
||||||
Anyification a{arena, rootScope, builtinTypes, &iceReporter, builtinTypes->anyType, builtinTypes->anyTypePack};
|
Anyification a{arena, rootScope, builtinTypes, &iceReporter, builtinTypes->anyType, builtinTypes->anyTypePack};
|
||||||
@ -446,12 +475,28 @@ void ConstraintSolver::finalizeModule()
|
|||||||
|
|
||||||
Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}};
|
Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}};
|
||||||
|
|
||||||
|
std::deque<TypeAndLocation> queue;
|
||||||
for (auto& [name, binding] : rootScope->bindings)
|
for (auto& [name, binding] : rootScope->bindings)
|
||||||
|
queue.push_back({binding.typeId, binding.location});
|
||||||
|
|
||||||
|
DenseHashSet<TypeId> seen{nullptr};
|
||||||
|
|
||||||
|
while (!queue.empty())
|
||||||
{
|
{
|
||||||
auto generalizedTy = u2.generalize(rootScope, binding.typeId);
|
TypeAndLocation binding = queue.front();
|
||||||
if (generalizedTy)
|
queue.pop_front();
|
||||||
binding.typeId = *generalizedTy;
|
|
||||||
else
|
TypeId ty = follow(binding.typeId);
|
||||||
|
|
||||||
|
if (seen.find(ty))
|
||||||
|
continue;
|
||||||
|
seen.insert(ty);
|
||||||
|
|
||||||
|
FreeTypeSearcher fts{&queue, binding.location};
|
||||||
|
fts.traverse(ty);
|
||||||
|
|
||||||
|
auto result = u2.generalize(rootScope, ty);
|
||||||
|
if (!result)
|
||||||
reportError(CodeTooComplex{}, binding.location);
|
reportError(CodeTooComplex{}, binding.location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2642,20 +2687,14 @@ ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypeId
|
|||||||
|
|
||||||
ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack)
|
ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack)
|
||||||
{
|
{
|
||||||
UnifierSharedState sharedState{&iceReporter};
|
Unifier2 u{arena, builtinTypes, NotNull{&iceReporter}};
|
||||||
Unifier u{normalizer, scope, Location{}, Covariant};
|
|
||||||
u.enableNewSolver();
|
|
||||||
|
|
||||||
u.tryUnify(subPack, superPack);
|
u.unify(subPack, superPack);
|
||||||
|
|
||||||
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
unblock(subPack, Location{});
|
||||||
|
unblock(superPack, Location{});
|
||||||
|
|
||||||
u.log.commit();
|
return {};
|
||||||
|
|
||||||
unblock(changedTypes, Location{});
|
|
||||||
unblock(changedPacks, Location{});
|
|
||||||
|
|
||||||
return std::move(u.errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NotNull<Constraint> ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)
|
NotNull<Constraint> ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)
|
||||||
|
@ -117,9 +117,7 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
|||||||
case DiffError::Kind::Normal:
|
case DiffError::Kind::Normal:
|
||||||
{
|
{
|
||||||
checkNonMissingPropertyLeavesHaveNulloptTableProperty();
|
checkNonMissingPropertyLeavesHaveNulloptTableProperty();
|
||||||
return pathStr + conditionalNewline
|
return pathStr + conditionalNewline + "has type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty);
|
||||||
+ "has type" + conditionalNewline
|
|
||||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
|
||||||
}
|
}
|
||||||
case DiffError::Kind::MissingTableProperty:
|
case DiffError::Kind::MissingTableProperty:
|
||||||
{
|
{
|
||||||
@ -127,17 +125,14 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
|||||||
{
|
{
|
||||||
if (!leaf.tableProperty.has_value())
|
if (!leaf.tableProperty.has_value())
|
||||||
throw InternalCompilerError{"leaf.tableProperty is nullopt"};
|
throw InternalCompilerError{"leaf.tableProperty is nullopt"};
|
||||||
return pathStr + "." + *leaf.tableProperty + conditionalNewline
|
return pathStr + "." + *leaf.tableProperty + conditionalNewline + "has type" + conditionalNewline + conditionalIndent +
|
||||||
+ "has type" + conditionalNewline
|
Luau::toString(*leaf.ty);
|
||||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
|
||||||
}
|
}
|
||||||
else if (otherLeaf.ty.has_value())
|
else if (otherLeaf.ty.has_value())
|
||||||
{
|
{
|
||||||
if (!otherLeaf.tableProperty.has_value())
|
if (!otherLeaf.tableProperty.has_value())
|
||||||
throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"};
|
throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"};
|
||||||
return pathStr + conditionalNewline
|
return pathStr + conditionalNewline + "is missing the property" + conditionalNewline + conditionalIndent + *otherLeaf.tableProperty;
|
||||||
+ "is missing the property" + conditionalNewline
|
|
||||||
+ conditionalIndent + *otherLeaf.tableProperty;
|
|
||||||
}
|
}
|
||||||
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
||||||
}
|
}
|
||||||
@ -148,15 +143,11 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
|||||||
{
|
{
|
||||||
if (!leaf.unionIndex.has_value())
|
if (!leaf.unionIndex.has_value())
|
||||||
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
||||||
return pathStr + conditionalNewline
|
return pathStr + conditionalNewline + "is a union containing type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty);
|
||||||
+ "is a union containing type" + conditionalNewline
|
|
||||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
|
||||||
}
|
}
|
||||||
else if (otherLeaf.ty.has_value())
|
else if (otherLeaf.ty.has_value())
|
||||||
{
|
{
|
||||||
return pathStr + conditionalNewline
|
return pathStr + conditionalNewline + "is a union missing type" + conditionalNewline + conditionalIndent + Luau::toString(*otherLeaf.ty);
|
||||||
+ "is a union missing type" + conditionalNewline
|
|
||||||
+ conditionalIndent + Luau::toString(*otherLeaf.ty);
|
|
||||||
}
|
}
|
||||||
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
||||||
}
|
}
|
||||||
@ -169,15 +160,13 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
|||||||
{
|
{
|
||||||
if (!leaf.unionIndex.has_value())
|
if (!leaf.unionIndex.has_value())
|
||||||
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
||||||
return pathStr + conditionalNewline
|
return pathStr + conditionalNewline + "is an intersection containing type" + conditionalNewline + conditionalIndent +
|
||||||
+ "is an intersection containing type" + conditionalNewline
|
Luau::toString(*leaf.ty);
|
||||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
|
||||||
}
|
}
|
||||||
else if (otherLeaf.ty.has_value())
|
else if (otherLeaf.ty.has_value())
|
||||||
{
|
{
|
||||||
return pathStr + conditionalNewline
|
return pathStr + conditionalNewline + "is an intersection missing type" + conditionalNewline + conditionalIndent +
|
||||||
+ "is an intersection missing type" + conditionalNewline
|
Luau::toString(*otherLeaf.ty);
|
||||||
+ conditionalIndent + Luau::toString(*otherLeaf.ty);
|
|
||||||
}
|
}
|
||||||
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"};
|
||||||
}
|
}
|
||||||
@ -185,15 +174,13 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
|||||||
{
|
{
|
||||||
if (!leaf.minLength.has_value())
|
if (!leaf.minLength.has_value())
|
||||||
throw InternalCompilerError{"leaf.minLength is nullopt"};
|
throw InternalCompilerError{"leaf.minLength is nullopt"};
|
||||||
return pathStr + conditionalNewline
|
return pathStr + conditionalNewline + "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments";
|
||||||
+ "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments";
|
|
||||||
}
|
}
|
||||||
case DiffError::Kind::LengthMismatchInFnRets:
|
case DiffError::Kind::LengthMismatchInFnRets:
|
||||||
{
|
{
|
||||||
if (!leaf.minLength.has_value())
|
if (!leaf.minLength.has_value())
|
||||||
throw InternalCompilerError{"leaf.minLength is nullopt"};
|
throw InternalCompilerError{"leaf.minLength is nullopt"};
|
||||||
return pathStr + conditionalNewline
|
return pathStr + conditionalNewline + "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values";
|
||||||
+ "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values";
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
@ -249,17 +236,15 @@ std::string DiffError::toString(bool multiLine) const
|
|||||||
case DiffError::Kind::IncompatibleGeneric:
|
case DiffError::Kind::IncompatibleGeneric:
|
||||||
{
|
{
|
||||||
std::string diffPathStr{diffPath.toString(true)};
|
std::string diffPathStr{diffPath.toString(true)};
|
||||||
return "DiffError: these two types are not equal because the left generic at" + conditionalNewline
|
return "DiffError: these two types are not equal because the left generic at" + conditionalNewline + conditionalIndent + leftRootName +
|
||||||
+ conditionalIndent + leftRootName + diffPathStr + conditionalNewline
|
diffPathStr + conditionalNewline + "cannot be the same type parameter as the right generic at" + conditionalNewline +
|
||||||
+ "cannot be the same type parameter as the right generic at" + conditionalNewline
|
conditionalIndent + rightRootName + diffPathStr;
|
||||||
+ conditionalIndent + rightRootName + diffPathStr;
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
return "DiffError: these two types are not equal because the left type at" + conditionalNewline
|
return "DiffError: these two types are not equal because the left type at" + conditionalNewline + conditionalIndent +
|
||||||
+ conditionalIndent + toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline +
|
toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline + "while the right type at" + conditionalNewline +
|
||||||
"while the right type at" + conditionalNewline
|
conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine);
|
||||||
+ conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1289,7 +1289,8 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
|||||||
|
|
||||||
if (result->timeout || result->cancelled)
|
if (result->timeout || result->cancelled)
|
||||||
{
|
{
|
||||||
// If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending types
|
// If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending
|
||||||
|
// types
|
||||||
ScopePtr moduleScope = result->getModuleScope();
|
ScopePtr moduleScope = result->getModuleScope();
|
||||||
moduleScope->returnType = builtinTypes->errorRecoveryTypePack();
|
moduleScope->returnType = builtinTypes->errorRecoveryTypePack();
|
||||||
|
|
||||||
|
@ -31,4 +31,4 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace Luau
|
||||||
|
@ -174,7 +174,8 @@ struct Replacer : Substitution
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<TypeId> instantiate(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty)
|
std::optional<TypeId> instantiate(
|
||||||
|
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
|
||||||
|
@ -2791,8 +2791,8 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
|
|||||||
else if (first == "native")
|
else if (first == "native")
|
||||||
{
|
{
|
||||||
if (space != std::string::npos)
|
if (space != std::string::npos)
|
||||||
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
|
emitWarning(
|
||||||
"native directive has extra symbols at the end of the line");
|
context, LintWarning::Code_CommentDirective, hc.location, "native directive has extra symbols at the end of the line");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -176,7 +176,7 @@ const NormalizedStringType NormalizedStringType::never;
|
|||||||
|
|
||||||
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr)
|
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr)
|
||||||
{
|
{
|
||||||
if (subStr.isUnion() && superStr.isUnion())
|
if (subStr.isUnion() && (superStr.isUnion() && !superStr.isNever()))
|
||||||
{
|
{
|
||||||
for (auto [name, ty] : subStr.singletons)
|
for (auto [name, ty] : subStr.singletons)
|
||||||
{
|
{
|
||||||
@ -1983,18 +1983,68 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
|||||||
|
|
||||||
void Normalizer::intersectStrings(NormalizedStringType& here, const NormalizedStringType& there)
|
void Normalizer::intersectStrings(NormalizedStringType& here, const NormalizedStringType& there)
|
||||||
{
|
{
|
||||||
|
/* There are 9 cases to worry about here
|
||||||
|
Normalized Left | Normalized Right
|
||||||
|
C1 string | string ===> trivial
|
||||||
|
C2 string - {u_1,..} | string ===> trivial
|
||||||
|
C3 {u_1, ..} | string ===> trivial
|
||||||
|
C4 string | string - {v_1, ..} ===> string - {v_1, ..}
|
||||||
|
C5 string - {u_1,..} | string - {v_1, ..} ===> string - ({u_s} U {v_s})
|
||||||
|
C6 {u_1, ..} | string - {v_1, ..} ===> {u_s} - {v_s}
|
||||||
|
C7 string | {v_1, ..} ===> {v_s}
|
||||||
|
C8 string - {u_1,..} | {v_1, ..} ===> {v_s} - {u_s}
|
||||||
|
C9 {u_1, ..} | {v_1, ..} ===> {u_s} ∩ {v_s}
|
||||||
|
*/
|
||||||
|
// Case 1,2,3
|
||||||
if (there.isString())
|
if (there.isString())
|
||||||
return;
|
return;
|
||||||
if (here.isString())
|
// Case 4, Case 7
|
||||||
here.resetToNever();
|
else if (here.isString())
|
||||||
|
|
||||||
for (auto it = here.singletons.begin(); it != here.singletons.end();)
|
|
||||||
{
|
{
|
||||||
if (there.singletons.count(it->first))
|
here.singletons.clear();
|
||||||
it++;
|
for (const auto& [key, type] : there.singletons)
|
||||||
else
|
here.singletons[key] = type;
|
||||||
it = here.singletons.erase(it);
|
here.isCofinite = here.isCofinite && there.isCofinite;
|
||||||
}
|
}
|
||||||
|
// Case 5
|
||||||
|
else if (here.isIntersection() && there.isIntersection())
|
||||||
|
{
|
||||||
|
here.isCofinite = true;
|
||||||
|
for (const auto& [key, type] : there.singletons)
|
||||||
|
here.singletons[key] = type;
|
||||||
|
}
|
||||||
|
// Case 6
|
||||||
|
else if (here.isUnion() && there.isIntersection())
|
||||||
|
{
|
||||||
|
here.isCofinite = false;
|
||||||
|
for (const auto& [key, _] : there.singletons)
|
||||||
|
here.singletons.erase(key);
|
||||||
|
}
|
||||||
|
// Case 8
|
||||||
|
else if (here.isIntersection() && there.isUnion())
|
||||||
|
{
|
||||||
|
here.isCofinite = false;
|
||||||
|
std::map<std::string, TypeId> result(there.singletons);
|
||||||
|
for (const auto& [key, _] : here.singletons)
|
||||||
|
result.erase(key);
|
||||||
|
here.singletons = result;
|
||||||
|
}
|
||||||
|
// Case 9
|
||||||
|
else if (here.isUnion() && there.isUnion())
|
||||||
|
{
|
||||||
|
here.isCofinite = false;
|
||||||
|
std::map<std::string, TypeId> result;
|
||||||
|
result.insert(here.singletons.begin(), here.singletons.end());
|
||||||
|
result.insert(there.singletons.begin(), there.singletons.end());
|
||||||
|
for (auto it = result.begin(); it != result.end();)
|
||||||
|
if (!here.singletons.count(it->first) || !there.singletons.count(it->first))
|
||||||
|
it = result.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
here.singletons = result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LUAU_ASSERT(0 && "Internal Error - unrecognized case");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, TypePackId there)
|
std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, TypePackId there)
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/Error.h"
|
#include "Luau/Error.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
|
#include "Luau/Scope.h"
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
@ -43,19 +44,21 @@ struct VarianceFlipper
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void SubtypingResult::andAlso(const SubtypingResult& other)
|
SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other)
|
||||||
{
|
{
|
||||||
isSubtype &= other.isSubtype;
|
isSubtype &= other.isSubtype;
|
||||||
// `|=` is intentional here, we want to preserve error related flags.
|
// `|=` is intentional here, we want to preserve error related flags.
|
||||||
isErrorSuppressing |= other.isErrorSuppressing;
|
isErrorSuppressing |= other.isErrorSuppressing;
|
||||||
normalizationTooComplex |= other.normalizationTooComplex;
|
normalizationTooComplex |= other.normalizationTooComplex;
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SubtypingResult::orElse(const SubtypingResult& other)
|
SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other)
|
||||||
{
|
{
|
||||||
isSubtype |= other.isSubtype;
|
isSubtype |= other.isSubtype;
|
||||||
isErrorSuppressing |= other.isErrorSuppressing;
|
isErrorSuppressing |= other.isErrorSuppressing;
|
||||||
normalizationTooComplex |= other.normalizationTooComplex;
|
normalizationTooComplex |= other.normalizationTooComplex;
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
|
SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
|
||||||
@ -88,9 +91,9 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
|||||||
mappedGenerics.clear();
|
mappedGenerics.clear();
|
||||||
mappedGenericPacks.clear();
|
mappedGenericPacks.clear();
|
||||||
|
|
||||||
SubtypingResult result = isSubtype_(subTy, superTy);
|
SubtypingResult result = isCovariantWith(subTy, superTy);
|
||||||
|
|
||||||
for (const auto& [subTy, bounds]: mappedGenerics)
|
for (const auto& [subTy, bounds] : mappedGenerics)
|
||||||
{
|
{
|
||||||
const auto& lb = bounds.lowerBound;
|
const auto& lb = bounds.lowerBound;
|
||||||
const auto& ub = bounds.upperBound;
|
const auto& ub = bounds.upperBound;
|
||||||
@ -98,7 +101,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
|||||||
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
|
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
|
||||||
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
|
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
|
||||||
|
|
||||||
result.andAlso(isSubtype_(lowerBound, upperBound));
|
result.andAlso(isCovariantWith(lowerBound, upperBound));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -106,7 +109,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
|||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp)
|
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp)
|
||||||
{
|
{
|
||||||
return isSubtype_(subTp, superTp);
|
return isCovariantWith(subTp, superTp);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@ -119,16 +122,17 @@ struct SeenSetPopper
|
|||||||
SeenSetPopper(Subtyping::SeenSet* seenTypes, std::pair<TypeId, TypeId> pair)
|
SeenSetPopper(Subtyping::SeenSet* seenTypes, std::pair<TypeId, TypeId> pair)
|
||||||
: seenTypes(seenTypes)
|
: seenTypes(seenTypes)
|
||||||
, pair(pair)
|
, pair(pair)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
~SeenSetPopper()
|
~SeenSetPopper()
|
||||||
{
|
{
|
||||||
seenTypes->erase(pair);
|
seenTypes->erase(pair);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, TypeId superTy)
|
||||||
{
|
{
|
||||||
subTy = follow(subTy);
|
subTy = follow(subTy);
|
||||||
superTy = follow(superTy);
|
superTy = follow(superTy);
|
||||||
@ -146,19 +150,27 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
|||||||
|
|
||||||
SeenSetPopper ssp{&seenTypes, typePair};
|
SeenSetPopper ssp{&seenTypes, typePair};
|
||||||
|
|
||||||
|
// Within the scope to which a generic belongs, that generic should be
|
||||||
|
// tested as though it were its upper bounds. We do not yet support bounded
|
||||||
|
// generics, so the upper bound is always unknown.
|
||||||
|
if (auto subGeneric = get<GenericType>(subTy); subGeneric && subsumes(subGeneric->scope, scope))
|
||||||
|
return isCovariantWith(builtinTypes->unknownType, superTy);
|
||||||
|
if (auto superGeneric = get<GenericType>(superTy); superGeneric && subsumes(superGeneric->scope, scope))
|
||||||
|
return isCovariantWith(subTy, builtinTypes->unknownType);
|
||||||
|
|
||||||
if (auto subUnion = get<UnionType>(subTy))
|
if (auto subUnion = get<UnionType>(subTy))
|
||||||
return isSubtype_(subUnion, superTy);
|
return isCovariantWith(subUnion, superTy);
|
||||||
else if (auto superUnion = get<UnionType>(superTy))
|
else if (auto superUnion = get<UnionType>(superTy))
|
||||||
return isSubtype_(subTy, superUnion);
|
return isCovariantWith(subTy, superUnion);
|
||||||
else if (auto superIntersection = get<IntersectionType>(superTy))
|
else if (auto superIntersection = get<IntersectionType>(superTy))
|
||||||
return isSubtype_(subTy, superIntersection);
|
return isCovariantWith(subTy, superIntersection);
|
||||||
else if (auto subIntersection = get<IntersectionType>(subTy))
|
else if (auto subIntersection = get<IntersectionType>(subTy))
|
||||||
{
|
{
|
||||||
SubtypingResult result = isSubtype_(subIntersection, superTy);
|
SubtypingResult result = isCovariantWith(subIntersection, superTy);
|
||||||
if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex)
|
if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex)
|
||||||
return result;
|
return result;
|
||||||
else
|
else
|
||||||
return isSubtype_(normalizer->normalize(subTy), normalizer->normalize(superTy));
|
return isCovariantWith(normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||||
}
|
}
|
||||||
else if (get<AnyType>(superTy))
|
else if (get<AnyType>(superTy))
|
||||||
return {true}; // This is always true.
|
return {true}; // This is always true.
|
||||||
@ -166,9 +178,7 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
|||||||
{
|
{
|
||||||
// any = unknown | error, so we rewrite this to match.
|
// any = unknown | error, so we rewrite this to match.
|
||||||
// As per TAPL: A | B <: T iff A <: T && B <: T
|
// As per TAPL: A | B <: T iff A <: T && B <: T
|
||||||
SubtypingResult result = isSubtype_(builtinTypes->unknownType, superTy);
|
return isCovariantWith(builtinTypes->unknownType, superTy).andAlso(isCovariantWith(builtinTypes->errorType, superTy));
|
||||||
result.andAlso(isSubtype_(builtinTypes->errorType, superTy));
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
else if (get<UnknownType>(superTy))
|
else if (get<UnknownType>(superTy))
|
||||||
{
|
{
|
||||||
@ -185,6 +195,12 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
|||||||
return {false, true};
|
return {false, true};
|
||||||
else if (get<ErrorType>(subTy))
|
else if (get<ErrorType>(subTy))
|
||||||
return {false, true};
|
return {false, true};
|
||||||
|
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
|
||||||
|
return isCovariantWith(p.first->ty, p.second->ty);
|
||||||
|
else if (auto subNegation = get<NegationType>(subTy))
|
||||||
|
return isCovariantWith(subNegation, superTy);
|
||||||
|
else if (auto superNegation = get<NegationType>(superTy))
|
||||||
|
return isCovariantWith(subTy, superNegation);
|
||||||
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
|
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
|
||||||
{
|
{
|
||||||
bool ok = bindGeneric(subTy, superTy);
|
bool ok = bindGeneric(subTy, superTy);
|
||||||
@ -196,32 +212,32 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
|||||||
return {ok};
|
return {ok};
|
||||||
}
|
}
|
||||||
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
|
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
|
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
|
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
|
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<TableType, TableType>(subTy, superTy))
|
else if (auto p = get2<TableType, TableType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy))
|
else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
|
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
|
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
|
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
|
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
|
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
|
||||||
return isSubtype_(p);
|
return isCovariantWith(p);
|
||||||
|
|
||||||
return {false};
|
return {false};
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||||
{
|
{
|
||||||
subTp = follow(subTp);
|
subTp = follow(subTp);
|
||||||
superTp = follow(superTp);
|
superTp = follow(superTp);
|
||||||
@ -241,7 +257,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
|||||||
|
|
||||||
for (size_t i = 0; i < headSize; ++i)
|
for (size_t i = 0; i < headSize; ++i)
|
||||||
{
|
{
|
||||||
results.push_back(isSubtype_(subHead[i], superHead[i]));
|
results.push_back(isCovariantWith(subHead[i], superHead[i]));
|
||||||
if (!results.back().isSubtype)
|
if (!results.back().isSubtype)
|
||||||
return {false};
|
return {false};
|
||||||
}
|
}
|
||||||
@ -255,7 +271,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
|||||||
if (auto vt = get<VariadicTypePack>(*subTail))
|
if (auto vt = get<VariadicTypePack>(*subTail))
|
||||||
{
|
{
|
||||||
for (size_t i = headSize; i < superHead.size(); ++i)
|
for (size_t i = headSize; i < superHead.size(); ++i)
|
||||||
results.push_back(isSubtype_(vt->ty, superHead[i]));
|
results.push_back(isCovariantWith(vt->ty, superHead[i]));
|
||||||
}
|
}
|
||||||
else if (auto gt = get<GenericTypePack>(*subTail))
|
else if (auto gt = get<GenericTypePack>(*subTail))
|
||||||
{
|
{
|
||||||
@ -266,11 +282,11 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
|||||||
// <X>(X) -> () <: (T) -> ()
|
// <X>(X) -> () <: (T) -> ()
|
||||||
|
|
||||||
// Possible optimization: If headSize == 0 then we can just use subTp as-is.
|
// Possible optimization: If headSize == 0 then we can just use subTp as-is.
|
||||||
std::vector<TypeId> headSlice(begin(superHead), end(superHead) + headSize);
|
std::vector<TypeId> headSlice(begin(superHead), begin(superHead) + headSize);
|
||||||
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
|
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
|
||||||
|
|
||||||
if (TypePackId* other = mappedGenericPacks.find(*subTail))
|
if (TypePackId* other = mappedGenericPacks.find(*subTail))
|
||||||
results.push_back(isSubtype_(*other, superTailPack));
|
results.push_back(isCovariantWith(*other, superTailPack));
|
||||||
else
|
else
|
||||||
mappedGenericPacks.try_insert(*subTail, superTailPack);
|
mappedGenericPacks.try_insert(*subTail, superTailPack);
|
||||||
|
|
||||||
@ -300,7 +316,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
|||||||
if (auto vt = get<VariadicTypePack>(*superTail))
|
if (auto vt = get<VariadicTypePack>(*superTail))
|
||||||
{
|
{
|
||||||
for (size_t i = headSize; i < subHead.size(); ++i)
|
for (size_t i = headSize; i < subHead.size(); ++i)
|
||||||
results.push_back(isSubtype_(subHead[i], vt->ty));
|
results.push_back(isCovariantWith(subHead[i], vt->ty));
|
||||||
}
|
}
|
||||||
else if (auto gt = get<GenericTypePack>(*superTail))
|
else if (auto gt = get<GenericTypePack>(*superTail))
|
||||||
{
|
{
|
||||||
@ -311,11 +327,11 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
|||||||
// <X...>(X...) -> () <: (T) -> ()
|
// <X...>(X...) -> () <: (T) -> ()
|
||||||
|
|
||||||
// Possible optimization: If headSize == 0 then we can just use subTp as-is.
|
// Possible optimization: If headSize == 0 then we can just use subTp as-is.
|
||||||
std::vector<TypeId> headSlice(begin(subHead), end(subHead) + headSize);
|
std::vector<TypeId> headSlice(begin(subHead), begin(subHead) + headSize);
|
||||||
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
|
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
|
||||||
|
|
||||||
if (TypePackId* other = mappedGenericPacks.find(*superTail))
|
if (TypePackId* other = mappedGenericPacks.find(*superTail))
|
||||||
results.push_back(isSubtype_(*other, subTailPack));
|
results.push_back(isCovariantWith(*other, subTailPack));
|
||||||
else
|
else
|
||||||
mappedGenericPacks.try_insert(*superTail, subTailPack);
|
mappedGenericPacks.try_insert(*superTail, subTailPack);
|
||||||
|
|
||||||
@ -344,7 +360,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
|||||||
{
|
{
|
||||||
if (auto p = get2<VariadicTypePack, VariadicTypePack>(*subTail, *superTail))
|
if (auto p = get2<VariadicTypePack, VariadicTypePack>(*subTail, *superTail))
|
||||||
{
|
{
|
||||||
results.push_back(isSubtype_(p));
|
results.push_back(isCovariantWith(p));
|
||||||
}
|
}
|
||||||
else if (auto p = get2<GenericTypePack, GenericTypePack>(*subTail, *superTail))
|
else if (auto p = get2<GenericTypePack, GenericTypePack>(*subTail, *superTail))
|
||||||
{
|
{
|
||||||
@ -380,7 +396,8 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
iceReporter->ice(format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str()));
|
iceReporter->ice(
|
||||||
|
format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str()));
|
||||||
}
|
}
|
||||||
else if (subTail)
|
else if (subTail)
|
||||||
{
|
{
|
||||||
@ -428,9 +445,33 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename SubTy, typename SuperTy>
|
template<typename SubTy, typename SuperTy>
|
||||||
SubtypingResult Subtyping::isSubtype_(const TryPair<const SubTy*, const SuperTy*>& pair)
|
SubtypingResult Subtyping::isContravariantWith(SubTy&& subTy, SuperTy&& superTy)
|
||||||
{
|
{
|
||||||
return isSubtype_(pair.first, pair.second);
|
return isCovariantWith(superTy, subTy);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SubTy, typename SuperTy>
|
||||||
|
SubtypingResult Subtyping::isInvariantWith(SubTy&& subTy, SuperTy&& superTy)
|
||||||
|
{
|
||||||
|
return isCovariantWith(subTy, superTy).andAlso(isContravariantWith(subTy, superTy));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SubTy, typename SuperTy>
|
||||||
|
SubtypingResult Subtyping::isCovariantWith(const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||||
|
{
|
||||||
|
return isCovariantWith(pair.first, pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SubTy, typename SuperTy>
|
||||||
|
SubtypingResult Subtyping::isContravariantWith(const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||||
|
{
|
||||||
|
return isCovariantWith(pair.second, pair.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename SubTy, typename SuperTy>
|
||||||
|
SubtypingResult Subtyping::isInvariantWith(const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||||
|
{
|
||||||
|
return isCovariantWith(pair).andAlso(isContravariantWith(pair));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -464,48 +505,219 @@ SubtypingResult Subtyping::isSubtype_(const TryPair<const SubTy*, const SuperTy*
|
|||||||
* other just asks for boolean ~ 'b. We can dispatch this and only commit
|
* other just asks for boolean ~ 'b. We can dispatch this and only commit
|
||||||
* boolean ~ 'b. This constraint does not teach us anything about 'a.
|
* boolean ~ 'b. This constraint does not teach us anything about 'a.
|
||||||
*/
|
*/
|
||||||
SubtypingResult Subtyping::isSubtype_(TypeId subTy, const UnionType* superUnion)
|
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, const UnionType* superUnion)
|
||||||
{
|
{
|
||||||
// As per TAPL: T <: A | B iff T <: A || T <: B
|
// As per TAPL: T <: A | B iff T <: A || T <: B
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
for (TypeId ty : superUnion)
|
for (TypeId ty : superUnion)
|
||||||
subtypings.push_back(isSubtype_(subTy, ty));
|
subtypings.push_back(isCovariantWith(subTy, ty));
|
||||||
return SubtypingResult::any(subtypings);
|
return SubtypingResult::any(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const UnionType* subUnion, TypeId superTy)
|
SubtypingResult Subtyping::isCovariantWith(const UnionType* subUnion, TypeId superTy)
|
||||||
{
|
{
|
||||||
// As per TAPL: A | B <: T iff A <: T && B <: T
|
// As per TAPL: A | B <: T iff A <: T && B <: T
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
for (TypeId ty : subUnion)
|
for (TypeId ty : subUnion)
|
||||||
subtypings.push_back(isSubtype_(ty, superTy));
|
subtypings.push_back(isCovariantWith(ty, superTy));
|
||||||
return SubtypingResult::all(subtypings);
|
return SubtypingResult::all(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(TypeId subTy, const IntersectionType* superIntersection)
|
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, const IntersectionType* superIntersection)
|
||||||
{
|
{
|
||||||
// As per TAPL: T <: A & B iff T <: A && T <: B
|
// As per TAPL: T <: A & B iff T <: A && T <: B
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
for (TypeId ty : superIntersection)
|
for (TypeId ty : superIntersection)
|
||||||
subtypings.push_back(isSubtype_(subTy, ty));
|
subtypings.push_back(isCovariantWith(subTy, ty));
|
||||||
return SubtypingResult::all(subtypings);
|
return SubtypingResult::all(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const IntersectionType* subIntersection, TypeId superTy)
|
SubtypingResult Subtyping::isCovariantWith(const IntersectionType* subIntersection, TypeId superTy)
|
||||||
{
|
{
|
||||||
// As per TAPL: A & B <: T iff A <: T || B <: T
|
// As per TAPL: A & B <: T iff A <: T || B <: T
|
||||||
std::vector<SubtypingResult> subtypings;
|
std::vector<SubtypingResult> subtypings;
|
||||||
for (TypeId ty : subIntersection)
|
for (TypeId ty : subIntersection)
|
||||||
subtypings.push_back(isSubtype_(ty, superTy));
|
subtypings.push_back(isCovariantWith(ty, superTy));
|
||||||
return SubtypingResult::any(subtypings);
|
return SubtypingResult::any(subtypings);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const PrimitiveType* superPrim)
|
SubtypingResult Subtyping::isCovariantWith(const NegationType* subNegation, TypeId superTy)
|
||||||
|
{
|
||||||
|
TypeId negatedTy = follow(subNegation->ty);
|
||||||
|
|
||||||
|
// In order to follow a consistent codepath, rather than folding the
|
||||||
|
// isCovariantWith test down to its conclusion here, we test the subtyping test
|
||||||
|
// of the result of negating the type for never, unknown, any, and error.
|
||||||
|
if (is<NeverType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬never ~ unknown
|
||||||
|
return isCovariantWith(builtinTypes->unknownType, superTy);
|
||||||
|
}
|
||||||
|
else if (is<UnknownType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬unknown ~ never
|
||||||
|
return isCovariantWith(builtinTypes->neverType, superTy);
|
||||||
|
}
|
||||||
|
else if (is<AnyType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬any ~ any
|
||||||
|
return isCovariantWith(negatedTy, superTy);
|
||||||
|
}
|
||||||
|
else if (auto u = get<UnionType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬(A ∪ B) ~ ¬A ∩ ¬B
|
||||||
|
// follow intersection rules: A & B <: T iff A <: T && B <: T
|
||||||
|
std::vector<SubtypingResult> subtypings;
|
||||||
|
|
||||||
|
for (TypeId ty : u)
|
||||||
|
{
|
||||||
|
NegationType negatedTmp{ty};
|
||||||
|
subtypings.push_back(isCovariantWith(&negatedTmp, superTy));
|
||||||
|
}
|
||||||
|
|
||||||
|
return SubtypingResult::all(subtypings);
|
||||||
|
}
|
||||||
|
else if (auto i = get<IntersectionType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬(A ∩ B) ~ ¬A ∪ ¬B
|
||||||
|
// follow union rules: A | B <: T iff A <: T || B <: T
|
||||||
|
std::vector<SubtypingResult> subtypings;
|
||||||
|
|
||||||
|
for (TypeId ty : i)
|
||||||
|
{
|
||||||
|
if (auto negatedPart = get<NegationType>(follow(ty)))
|
||||||
|
subtypings.push_back(isCovariantWith(negatedPart->ty, superTy));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NegationType negatedTmp{ty};
|
||||||
|
subtypings.push_back(isCovariantWith(&negatedTmp, superTy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SubtypingResult::any(subtypings);
|
||||||
|
}
|
||||||
|
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
|
||||||
|
{
|
||||||
|
iceReporter->ice("attempting to negate a non-testable type");
|
||||||
|
}
|
||||||
|
// negating a different subtype will get you a very wide type that's not a
|
||||||
|
// subtype of other stuff.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return {false};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SubtypingResult Subtyping::isCovariantWith(const TypeId subTy, const NegationType* superNegation)
|
||||||
|
{
|
||||||
|
TypeId negatedTy = follow(superNegation->ty);
|
||||||
|
|
||||||
|
if (is<NeverType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬never ~ unknown
|
||||||
|
return isCovariantWith(subTy, builtinTypes->unknownType);
|
||||||
|
}
|
||||||
|
else if (is<UnknownType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬unknown ~ never
|
||||||
|
return isCovariantWith(subTy, builtinTypes->neverType);
|
||||||
|
}
|
||||||
|
else if (is<AnyType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬any ~ any
|
||||||
|
return isSubtype(subTy, negatedTy);
|
||||||
|
}
|
||||||
|
else if (auto u = get<UnionType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬(A ∪ B) ~ ¬A ∩ ¬B
|
||||||
|
// follow intersection rules: A & B <: T iff A <: T && B <: T
|
||||||
|
std::vector<SubtypingResult> subtypings;
|
||||||
|
|
||||||
|
for (TypeId ty : u)
|
||||||
|
{
|
||||||
|
if (auto negatedPart = get<NegationType>(follow(ty)))
|
||||||
|
subtypings.push_back(isCovariantWith(subTy, negatedPart->ty));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NegationType negatedTmp{ty};
|
||||||
|
subtypings.push_back(isCovariantWith(subTy, &negatedTmp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SubtypingResult::all(subtypings);
|
||||||
|
}
|
||||||
|
else if (auto i = get<IntersectionType>(negatedTy))
|
||||||
|
{
|
||||||
|
// ¬(A ∩ B) ~ ¬A ∪ ¬B
|
||||||
|
// follow union rules: A | B <: T iff A <: T || B <: T
|
||||||
|
std::vector<SubtypingResult> subtypings;
|
||||||
|
|
||||||
|
for (TypeId ty : i)
|
||||||
|
{
|
||||||
|
if (auto negatedPart = get<NegationType>(follow(ty)))
|
||||||
|
subtypings.push_back(isCovariantWith(subTy, negatedPart->ty));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NegationType negatedTmp{ty};
|
||||||
|
subtypings.push_back(isCovariantWith(subTy, &negatedTmp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SubtypingResult::any(subtypings);
|
||||||
|
}
|
||||||
|
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, negatedTy))
|
||||||
|
{
|
||||||
|
// number <: ¬boolean
|
||||||
|
// number </: ¬number
|
||||||
|
return {p.first->type != p.second->type};
|
||||||
|
}
|
||||||
|
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, negatedTy))
|
||||||
|
{
|
||||||
|
// "foo" </: ¬string
|
||||||
|
if (get<StringSingleton>(p.first) && p.second->type == PrimitiveType::String)
|
||||||
|
return {false};
|
||||||
|
// false </: ¬boolean
|
||||||
|
else if (get<BooleanSingleton>(p.first) && p.second->type == PrimitiveType::Boolean)
|
||||||
|
return {false};
|
||||||
|
// other cases are true
|
||||||
|
else
|
||||||
|
return {true};
|
||||||
|
}
|
||||||
|
else if (auto p = get2<PrimitiveType, SingletonType>(subTy, negatedTy))
|
||||||
|
{
|
||||||
|
if (p.first->type == PrimitiveType::String && get<StringSingleton>(p.second))
|
||||||
|
return {false};
|
||||||
|
else if (p.first->type == PrimitiveType::Boolean && get<BooleanSingleton>(p.second))
|
||||||
|
return {false};
|
||||||
|
else
|
||||||
|
return {true};
|
||||||
|
}
|
||||||
|
// the top class type is not actually a primitive type, so the negation of
|
||||||
|
// any one of them includes the top class type.
|
||||||
|
else if (auto p = get2<ClassType, PrimitiveType>(subTy, negatedTy))
|
||||||
|
return {true};
|
||||||
|
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
|
||||||
|
return {p->type != PrimitiveType::Table};
|
||||||
|
else if (auto p = get2<FunctionType, PrimitiveType>(subTy, negatedTy))
|
||||||
|
return {p.second->type != PrimitiveType::Function};
|
||||||
|
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
|
||||||
|
return {*p.first != *p.second};
|
||||||
|
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy))
|
||||||
|
return SubtypingResult::negate(isCovariantWith(p.first, p.second));
|
||||||
|
else if (get2<FunctionType, ClassType>(subTy, negatedTy))
|
||||||
|
return {true};
|
||||||
|
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
|
||||||
|
iceReporter->ice("attempting to negate a non-testable type");
|
||||||
|
|
||||||
|
return {false};
|
||||||
|
}
|
||||||
|
|
||||||
|
SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const PrimitiveType* superPrim)
|
||||||
{
|
{
|
||||||
return {subPrim->type == superPrim->type};
|
return {subPrim->type == superPrim->type};
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const PrimitiveType* superPrim)
|
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const PrimitiveType* superPrim)
|
||||||
{
|
{
|
||||||
if (get<StringSingleton>(subSingleton) && superPrim->type == PrimitiveType::String)
|
if (get<StringSingleton>(subSingleton) && superPrim->type == PrimitiveType::String)
|
||||||
return {true};
|
return {true};
|
||||||
@ -515,24 +727,20 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const P
|
|||||||
return {false};
|
return {false};
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton)
|
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const SingletonType* superSingleton)
|
||||||
{
|
{
|
||||||
return {*subSingleton == *superSingleton};
|
return {*subSingleton == *superSingleton};
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType* superTable)
|
SubtypingResult Subtyping::isCovariantWith(const TableType* subTable, const TableType* superTable)
|
||||||
{
|
{
|
||||||
SubtypingResult result{true};
|
SubtypingResult result{true};
|
||||||
|
|
||||||
for (const auto& [name, prop]: superTable->props)
|
for (const auto& [name, prop] : superTable->props)
|
||||||
{
|
{
|
||||||
auto it = subTable->props.find(name);
|
auto it = subTable->props.find(name);
|
||||||
if (it != subTable->props.end())
|
if (it != subTable->props.end())
|
||||||
{
|
result.andAlso(isInvariantWith(prop.type(), it->second.type()));
|
||||||
// Table properties are invariant
|
|
||||||
result.andAlso(isSubtype(it->second.type(), prop.type()));
|
|
||||||
result.andAlso(isSubtype(prop.type(), it->second.type()));
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
return SubtypingResult{false};
|
return SubtypingResult{false};
|
||||||
}
|
}
|
||||||
@ -540,17 +748,18 @@ SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const MetatableType* superMt)
|
SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const MetatableType* superMt)
|
||||||
{
|
{
|
||||||
return SubtypingResult::all({
|
return SubtypingResult::all({
|
||||||
isSubtype_(subMt->table, superMt->table),
|
isCovariantWith(subMt->table, superMt->table),
|
||||||
isSubtype_(subMt->metatable, superMt->metatable),
|
isCovariantWith(subMt->metatable, superMt->metatable),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableType* superTable)
|
SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const TableType* superTable)
|
||||||
{
|
{
|
||||||
if (auto subTable = get<TableType>(subMt->table)) {
|
if (auto subTable = get<TableType>(subMt->table))
|
||||||
|
{
|
||||||
// Metatables cannot erase properties from the table they're attached to, so
|
// Metatables cannot erase properties from the table they're attached to, so
|
||||||
// the subtyping rule for this is just if the table component is a subtype
|
// the subtyping rule for this is just if the table component is a subtype
|
||||||
// of the supertype table.
|
// of the supertype table.
|
||||||
@ -560,7 +769,7 @@ SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableTyp
|
|||||||
// that the metatable isn't a subtype of the table, even though they have
|
// that the metatable isn't a subtype of the table, even though they have
|
||||||
// compatible properties/shapes. We'll revisit this later when we have a
|
// compatible properties/shapes. We'll revisit this later when we have a
|
||||||
// better understanding of how important this is.
|
// better understanding of how important this is.
|
||||||
return isSubtype_(subTable, superTable);
|
return isCovariantWith(subTable, superTable);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -569,23 +778,19 @@ SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const TableTyp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const ClassType* superClass)
|
SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const ClassType* superClass)
|
||||||
{
|
{
|
||||||
return {isSubclass(subClass, superClass)};
|
return {isSubclass(subClass, superClass)};
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType* superTable)
|
SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const TableType* superTable)
|
||||||
{
|
{
|
||||||
SubtypingResult result{true};
|
SubtypingResult result{true};
|
||||||
|
|
||||||
for (const auto& [name, prop]: superTable->props)
|
for (const auto& [name, prop] : superTable->props)
|
||||||
{
|
{
|
||||||
if (auto classProp = lookupClassProp(subClass, name))
|
if (auto classProp = lookupClassProp(subClass, name))
|
||||||
{
|
result.andAlso(isInvariantWith(prop.type(), classProp->type()));
|
||||||
// Table properties are invariant
|
|
||||||
result.andAlso(isSubtype_(classProp->type(), prop.type()));
|
|
||||||
result.andAlso(isSubtype_(prop.type(), classProp->type()));
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
return SubtypingResult{false};
|
return SubtypingResult{false};
|
||||||
}
|
}
|
||||||
@ -593,20 +798,20 @@ SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction)
|
SubtypingResult Subtyping::isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction)
|
||||||
{
|
{
|
||||||
SubtypingResult result;
|
SubtypingResult result;
|
||||||
{
|
{
|
||||||
VarianceFlipper vf{&variance};
|
VarianceFlipper vf{&variance};
|
||||||
result.orElse(isSubtype_(superFunction->argTypes, subFunction->argTypes));
|
result.orElse(isContravariantWith(subFunction->argTypes, superFunction->argTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
result.andAlso(isSubtype_(subFunction->retTypes, superFunction->retTypes));
|
result.andAlso(isCovariantWith(subFunction->retTypes, superFunction->retTypes));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableType* superTable)
|
SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable)
|
||||||
{
|
{
|
||||||
SubtypingResult result{false};
|
SubtypingResult result{false};
|
||||||
if (subPrim->type == PrimitiveType::String)
|
if (subPrim->type == PrimitiveType::String)
|
||||||
@ -618,7 +823,7 @@ SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableT
|
|||||||
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
|
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
|
||||||
{
|
{
|
||||||
if (auto stringTable = get<TableType>(it->second.type()))
|
if (auto stringTable = get<TableType>(it->second.type()))
|
||||||
result.orElse(isSubtype_(stringTable, superTable));
|
result.orElse(isCovariantWith(stringTable, superTable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -627,7 +832,7 @@ SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableT
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const TableType* superTable)
|
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const TableType* superTable)
|
||||||
{
|
{
|
||||||
SubtypingResult result{false};
|
SubtypingResult result{false};
|
||||||
if (auto stringleton = get<StringSingleton>(subSingleton))
|
if (auto stringleton = get<StringSingleton>(subSingleton))
|
||||||
@ -639,7 +844,7 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const T
|
|||||||
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
|
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
|
||||||
{
|
{
|
||||||
if (auto stringTable = get<TableType>(it->second.type()))
|
if (auto stringTable = get<TableType>(it->second.type()))
|
||||||
result.orElse(isSubtype_(stringTable, superTable));
|
result.orElse(isCovariantWith(stringTable, superTable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -647,29 +852,27 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const T
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm)
|
SubtypingResult Subtyping::isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm)
|
||||||
{
|
{
|
||||||
if (!subNorm || !superNorm)
|
if (!subNorm || !superNorm)
|
||||||
return {false, true, true};
|
return {false, true, true};
|
||||||
|
|
||||||
SubtypingResult result = isSubtype_(subNorm->tops, superNorm->tops);
|
SubtypingResult result = isCovariantWith(subNorm->tops, superNorm->tops);
|
||||||
result.andAlso(isSubtype_(subNorm->booleans, superNorm->booleans));
|
result.andAlso(isCovariantWith(subNorm->booleans, superNorm->booleans));
|
||||||
result.andAlso(isSubtype_(subNorm->classes, superNorm->classes, superNorm->tables));
|
result.andAlso(isCovariantWith(subNorm->classes, superNorm->classes).orElse(isCovariantWith(subNorm->classes, superNorm->tables)));
|
||||||
result.andAlso(isSubtype_(subNorm->errors, superNorm->errors));
|
result.andAlso(isCovariantWith(subNorm->errors, superNorm->errors));
|
||||||
result.andAlso(isSubtype_(subNorm->nils, superNorm->nils));
|
result.andAlso(isCovariantWith(subNorm->nils, superNorm->nils));
|
||||||
result.andAlso(isSubtype_(subNorm->numbers, superNorm->numbers));
|
result.andAlso(isCovariantWith(subNorm->numbers, superNorm->numbers));
|
||||||
result.isSubtype &= Luau::isSubtype(subNorm->strings, superNorm->strings);
|
result.andAlso(isCovariantWith(subNorm->strings, superNorm->strings));
|
||||||
// isSubtype_(subNorm->strings, superNorm->tables);
|
result.andAlso(isCovariantWith(subNorm->strings, superNorm->tables));
|
||||||
result.andAlso(isSubtype_(subNorm->threads, superNorm->threads));
|
result.andAlso(isCovariantWith(subNorm->threads, superNorm->threads));
|
||||||
result.andAlso(isSubtype_(subNorm->tables, superNorm->tables));
|
result.andAlso(isCovariantWith(subNorm->tables, superNorm->tables));
|
||||||
// isSubtype_(subNorm->tables, superNorm->strings);
|
result.andAlso(isCovariantWith(subNorm->functions, superNorm->functions));
|
||||||
// isSubtype_(subNorm->tables, superNorm->classes);
|
// isCovariantWith(subNorm->tyvars, superNorm->tyvars);
|
||||||
result.andAlso(isSubtype_(subNorm->functions, superNorm->functions));
|
|
||||||
// isSubtype_(subNorm->tyvars, superNorm->tyvars);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables)
|
SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const NormalizedClassType& superClass)
|
||||||
{
|
{
|
||||||
for (const auto& [subClassTy, _] : subClass.classes)
|
for (const auto& [subClassTy, _] : subClass.classes)
|
||||||
{
|
{
|
||||||
@ -677,24 +880,18 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const
|
|||||||
|
|
||||||
for (const auto& [superClassTy, superNegations] : superClass.classes)
|
for (const auto& [superClassTy, superNegations] : superClass.classes)
|
||||||
{
|
{
|
||||||
result.orElse(isSubtype_(subClassTy, superClassTy));
|
result.orElse(isCovariantWith(subClassTy, superClassTy));
|
||||||
if (!result.isSubtype)
|
if (!result.isSubtype)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (TypeId negation : superNegations)
|
for (TypeId negation : superNegations)
|
||||||
{
|
{
|
||||||
result.andAlso(SubtypingResult::negate(isSubtype_(subClassTy, negation)));
|
result.andAlso(SubtypingResult::negate(isCovariantWith(subClassTy, negation)));
|
||||||
if (result.isSubtype)
|
if (result.isSubtype)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.isSubtype)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (TypeId superTableTy : superTables)
|
|
||||||
result.orElse(isSubtype_(subClassTy, superTableTy));
|
|
||||||
|
|
||||||
if (!result.isSubtype)
|
if (!result.isSubtype)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -702,17 +899,79 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const
|
|||||||
return {true};
|
return {true};
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction)
|
SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const TypeIds& superTables)
|
||||||
|
{
|
||||||
|
for (const auto& [subClassTy, _] : subClass.classes)
|
||||||
|
{
|
||||||
|
SubtypingResult result;
|
||||||
|
|
||||||
|
for (TypeId superTableTy : superTables)
|
||||||
|
result.orElse(isCovariantWith(subClassTy, superTableTy));
|
||||||
|
|
||||||
|
if (!result.isSubtype)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {true};
|
||||||
|
}
|
||||||
|
|
||||||
|
SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString, const NormalizedStringType& superString)
|
||||||
|
{
|
||||||
|
bool isSubtype = Luau::isSubtype(subString, superString);
|
||||||
|
return {isSubtype};
|
||||||
|
}
|
||||||
|
|
||||||
|
SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString, const TypeIds& superTables)
|
||||||
|
{
|
||||||
|
if (subString.isNever())
|
||||||
|
return {true};
|
||||||
|
|
||||||
|
if (subString.isCofinite)
|
||||||
|
{
|
||||||
|
SubtypingResult result;
|
||||||
|
for (const auto& superTable : superTables)
|
||||||
|
{
|
||||||
|
result.orElse(isCovariantWith(builtinTypes->stringType, superTable));
|
||||||
|
if (result.isSubtype)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finite case
|
||||||
|
// S = s1 | s2 | s3 ... sn <: t1 | t2 | ... | tn
|
||||||
|
// iff for some ti, S <: ti
|
||||||
|
// iff for all sj, sj <: ti
|
||||||
|
for (const auto& superTable : superTables)
|
||||||
|
{
|
||||||
|
SubtypingResult result{true};
|
||||||
|
for (const auto& [_, subString] : subString.singletons)
|
||||||
|
{
|
||||||
|
result.andAlso(isCovariantWith(subString, superTable));
|
||||||
|
if (!result.isSubtype)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.isSubtype)
|
||||||
|
continue;
|
||||||
|
else
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {false};
|
||||||
|
}
|
||||||
|
|
||||||
|
SubtypingResult Subtyping::isCovariantWith(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction)
|
||||||
{
|
{
|
||||||
if (subFunction.isNever())
|
if (subFunction.isNever())
|
||||||
return {true};
|
return {true};
|
||||||
else if (superFunction.isTop)
|
else if (superFunction.isTop)
|
||||||
return {true};
|
return {true};
|
||||||
else
|
else
|
||||||
return isSubtype_(subFunction.parts, superFunction.parts);
|
return isCovariantWith(subFunction.parts, superFunction.parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes)
|
SubtypingResult Subtyping::isCovariantWith(const TypeIds& subTypes, const TypeIds& superTypes)
|
||||||
{
|
{
|
||||||
std::vector<SubtypingResult> results;
|
std::vector<SubtypingResult> results;
|
||||||
|
|
||||||
@ -720,15 +979,15 @@ SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& su
|
|||||||
{
|
{
|
||||||
results.emplace_back();
|
results.emplace_back();
|
||||||
for (TypeId superTy : superTypes)
|
for (TypeId superTy : superTypes)
|
||||||
results.back().orElse(isSubtype_(subTy, superTy));
|
results.back().orElse(isCovariantWith(subTy, superTy));
|
||||||
}
|
}
|
||||||
|
|
||||||
return SubtypingResult::all(results);
|
return SubtypingResult::all(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isSubtype_(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic)
|
SubtypingResult Subtyping::isCovariantWith(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic)
|
||||||
{
|
{
|
||||||
return isSubtype_(subVariadic->ty, superVariadic->ty);
|
return isCovariantWith(subVariadic->ty, superVariadic->ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy)
|
bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy)
|
||||||
@ -772,7 +1031,7 @@ bool Subtyping::bindGeneric(TypePackId subTp, TypePackId superTp)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename Container>
|
template<typename T, typename Container>
|
||||||
TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
|
TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
|
||||||
{
|
{
|
||||||
if (container.empty())
|
if (container.empty())
|
||||||
|
@ -145,8 +145,7 @@ void StateDot::visitChildren(TypeId ty, int index)
|
|||||||
startNode(index);
|
startNode(index);
|
||||||
startNodeLabel();
|
startNodeLabel();
|
||||||
|
|
||||||
auto go = [&](auto&& t)
|
auto go = [&](auto&& t) {
|
||||||
{
|
|
||||||
using T = std::decay_t<decltype(t)>;
|
using T = std::decay_t<decltype(t)>;
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, BoundType>)
|
if constexpr (std::is_same_v<T, BoundType>)
|
||||||
|
@ -242,8 +242,8 @@ struct TypeChecker2
|
|||||||
|
|
||||||
Normalizer normalizer;
|
Normalizer normalizer;
|
||||||
|
|
||||||
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule* sourceModule,
|
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
|
||||||
Module* module)
|
const SourceModule* sourceModule, Module* module)
|
||||||
: builtinTypes(builtinTypes)
|
: builtinTypes(builtinTypes)
|
||||||
, logger(logger)
|
, logger(logger)
|
||||||
, limits(limits)
|
, limits(limits)
|
||||||
@ -1295,13 +1295,8 @@ struct TypeChecker2
|
|||||||
else if (auto assertion = expr->as<AstExprTypeAssertion>())
|
else if (auto assertion = expr->as<AstExprTypeAssertion>())
|
||||||
return isLiteral(assertion->expr);
|
return isLiteral(assertion->expr);
|
||||||
|
|
||||||
return
|
return expr->is<AstExprConstantNil>() || expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNumber>() ||
|
||||||
expr->is<AstExprConstantNil>() ||
|
expr->is<AstExprConstantString>() || expr->is<AstExprFunction>() || expr->is<AstExprTable>();
|
||||||
expr->is<AstExprConstantBool>() ||
|
|
||||||
expr->is<AstExprConstantNumber>() ||
|
|
||||||
expr->is<AstExprConstantString>() ||
|
|
||||||
expr->is<AstExprFunction>() ||
|
|
||||||
expr->is<AstExprTable>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::unique_ptr<LiteralProperties> buildLiteralPropertiesSet(AstExpr* expr)
|
static std::unique_ptr<LiteralProperties> buildLiteralPropertiesSet(AstExpr* expr)
|
||||||
@ -2686,8 +2681,8 @@ struct TypeChecker2
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void check(
|
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
|
||||||
NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule& sourceModule, Module* module)
|
const SourceModule& sourceModule, Module* module)
|
||||||
{
|
{
|
||||||
TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module};
|
TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module};
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
|
|||||||
LUAU_FASTFLAG(LuauNormalizeBlockedTypes)
|
LUAU_FASTFLAG(LuauNormalizeBlockedTypes)
|
||||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls)
|
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls)
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -2285,7 +2286,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
|
|||||||
variance = Invariant;
|
variance = Invariant;
|
||||||
|
|
||||||
Unifier innerState = makeChildUnifier();
|
Unifier innerState = makeChildUnifier();
|
||||||
if (useNewSolver)
|
if (useNewSolver || FFlag::LuauFixIndexerSubtypingOrdering)
|
||||||
innerState.tryUnify_(prop.type(), superTable->indexer->indexResultType);
|
innerState.tryUnify_(prop.type(), superTable->indexer->indexResultType);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,6 @@ Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes,
|
|||||||
, ice(ice)
|
, ice(ice)
|
||||||
, recursionLimit(FInt::LuauTypeInferRecursionLimit)
|
, recursionLimit(FInt::LuauTypeInferRecursionLimit)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||||
@ -99,10 +98,7 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t maxLength = std::max(
|
size_t maxLength = std::max(flatten(subTp).first.size(), flatten(superTp).first.size());
|
||||||
flatten(subTp).first.size(),
|
|
||||||
flatten(superTp).first.size()
|
|
||||||
);
|
|
||||||
|
|
||||||
auto [subTypes, subTail] = extendTypePack(*arena, builtinTypes, subTp, maxLength);
|
auto [subTypes, subTail] = extendTypePack(*arena, builtinTypes, subTp, maxLength);
|
||||||
auto [superTypes, superTail] = extendTypePack(*arena, builtinTypes, superTp, maxLength);
|
auto [superTypes, superTail] = extendTypePack(*arena, builtinTypes, superTp, maxLength);
|
||||||
@ -123,16 +119,25 @@ struct FreeTypeSearcher : TypeVisitor
|
|||||||
explicit FreeTypeSearcher(NotNull<Scope> scope)
|
explicit FreeTypeSearcher(NotNull<Scope> scope)
|
||||||
: TypeVisitor(/*skipBoundTypes*/ true)
|
: TypeVisitor(/*skipBoundTypes*/ true)
|
||||||
, scope(scope)
|
, scope(scope)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
enum { Positive, Negative } polarity = Positive;
|
enum
|
||||||
|
{
|
||||||
|
Positive,
|
||||||
|
Negative
|
||||||
|
} polarity = Positive;
|
||||||
|
|
||||||
void flip()
|
void flip()
|
||||||
{
|
{
|
||||||
switch (polarity)
|
switch (polarity)
|
||||||
{
|
{
|
||||||
case Positive: polarity = Negative; break;
|
case Positive:
|
||||||
case Negative: polarity = Positive; break;
|
polarity = Negative;
|
||||||
|
break;
|
||||||
|
case Negative:
|
||||||
|
polarity = Positive;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,8 +157,12 @@ struct FreeTypeSearcher : TypeVisitor
|
|||||||
|
|
||||||
switch (polarity)
|
switch (polarity)
|
||||||
{
|
{
|
||||||
case Positive: positiveTypes.insert(ty); break;
|
case Positive:
|
||||||
case Negative: negativeTypes.insert(ty); break;
|
positiveTypes.insert(ty);
|
||||||
|
break;
|
||||||
|
case Negative:
|
||||||
|
negativeTypes.insert(ty);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -180,13 +189,17 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||||||
std::unordered_set<TypeId> negativeTypes;
|
std::unordered_set<TypeId> negativeTypes;
|
||||||
std::vector<TypeId> generics;
|
std::vector<TypeId> generics;
|
||||||
|
|
||||||
MutatingGeneralizer(NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, std::unordered_set<TypeId> positiveTypes, std::unordered_set<TypeId> negativeTypes)
|
bool isWithinFunction = false;
|
||||||
|
|
||||||
|
MutatingGeneralizer(
|
||||||
|
NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, std::unordered_set<TypeId> positiveTypes, std::unordered_set<TypeId> negativeTypes)
|
||||||
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
||||||
, builtinTypes(builtinTypes)
|
, builtinTypes(builtinTypes)
|
||||||
, scope(scope)
|
, scope(scope)
|
||||||
, positiveTypes(std::move(positiveTypes))
|
, positiveTypes(std::move(positiveTypes))
|
||||||
, negativeTypes(std::move(negativeTypes))
|
, negativeTypes(std::move(negativeTypes))
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
static void replace(DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle, TypeId replacement)
|
static void replace(DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle, TypeId replacement)
|
||||||
{
|
{
|
||||||
@ -211,10 +224,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||||||
// FIXME: I bet this function has reentrancy problems
|
// FIXME: I bet this function has reentrancy problems
|
||||||
option = follow(option);
|
option = follow(option);
|
||||||
if (option == needle)
|
if (option == needle)
|
||||||
{
|
|
||||||
LUAU_ASSERT(!seen.find(option));
|
|
||||||
option = replacement;
|
option = replacement;
|
||||||
}
|
|
||||||
|
|
||||||
// TODO seen set
|
// TODO seen set
|
||||||
else if (get<UnionType>(option))
|
else if (get<UnionType>(option))
|
||||||
@ -224,7 +234,21 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visit (TypeId ty, const FreeType&) override
|
bool visit(TypeId ty, const FunctionType& ft) override
|
||||||
|
{
|
||||||
|
const bool oldValue = isWithinFunction;
|
||||||
|
|
||||||
|
isWithinFunction = true;
|
||||||
|
|
||||||
|
traverse(ft.argTypes);
|
||||||
|
traverse(ft.retTypes);
|
||||||
|
|
||||||
|
isWithinFunction = oldValue;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const FreeType&) override
|
||||||
{
|
{
|
||||||
const FreeType* ft = get<FreeType>(ty);
|
const FreeType* ft = get<FreeType>(ty);
|
||||||
LUAU_ASSERT(ft);
|
LUAU_ASSERT(ft);
|
||||||
@ -232,7 +256,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||||||
traverse(ft->lowerBound);
|
traverse(ft->lowerBound);
|
||||||
traverse(ft->upperBound);
|
traverse(ft->upperBound);
|
||||||
|
|
||||||
// ft is potentially invalid now.
|
// It is possible for the above traverse() calls to cause ty to be
|
||||||
|
// transmuted. We must reaquire ft if this happens.
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
ft = get<FreeType>(ty);
|
ft = get<FreeType>(ty);
|
||||||
if (!ft)
|
if (!ft)
|
||||||
@ -251,10 +276,15 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||||||
seen.insert(ty);
|
seen.insert(ty);
|
||||||
|
|
||||||
if (!hasLowerBound && !hasUpperBound)
|
if (!hasLowerBound && !hasUpperBound)
|
||||||
|
{
|
||||||
|
if (isWithinFunction)
|
||||||
{
|
{
|
||||||
emplaceType<GenericType>(asMutable(ty), scope);
|
emplaceType<GenericType>(asMutable(ty), scope);
|
||||||
generics.push_back(ty);
|
generics.push_back(ty);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
emplaceType<BoundType>(asMutable(ty), builtinTypes->unknownType);
|
||||||
|
}
|
||||||
|
|
||||||
// It is possible that this free type has other free types in its upper
|
// It is possible that this free type has other free types in its upper
|
||||||
// or lower bounds. If this is the case, we must replace those
|
// or lower bounds. If this is the case, we must replace those
|
||||||
@ -264,19 +294,27 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||||||
// If we do not do this, we get tautological bounds like a <: a <: unknown.
|
// If we do not do this, we get tautological bounds like a <: a <: unknown.
|
||||||
else if (isPositive && !hasUpperBound)
|
else if (isPositive && !hasUpperBound)
|
||||||
{
|
{
|
||||||
if (FreeType* lowerFree = getMutable<FreeType>(ft->lowerBound); lowerFree && lowerFree->upperBound == ty)
|
TypeId lb = follow(ft->lowerBound);
|
||||||
|
if (FreeType* lowerFree = getMutable<FreeType>(lb); lowerFree && lowerFree->upperBound == ty)
|
||||||
lowerFree->upperBound = builtinTypes->unknownType;
|
lowerFree->upperBound = builtinTypes->unknownType;
|
||||||
else
|
else
|
||||||
replace(seen, ft->lowerBound, ty, builtinTypes->unknownType);
|
{
|
||||||
emplaceType<BoundType>(asMutable(ty), ft->lowerBound);
|
DenseHashSet<TypeId> replaceSeen{nullptr};
|
||||||
|
replace(replaceSeen, lb, ty, builtinTypes->unknownType);
|
||||||
|
}
|
||||||
|
emplaceType<BoundType>(asMutable(ty), lb);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (FreeType* upperFree = getMutable<FreeType>(ft->upperBound); upperFree && upperFree->lowerBound == ty)
|
TypeId ub = follow(ft->upperBound);
|
||||||
|
if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == ty)
|
||||||
upperFree->lowerBound = builtinTypes->neverType;
|
upperFree->lowerBound = builtinTypes->neverType;
|
||||||
else
|
else
|
||||||
replace(seen, ft->upperBound, ty, builtinTypes->neverType);
|
{
|
||||||
emplaceType<BoundType>(asMutable(ty), ft->upperBound);
|
DenseHashSet<TypeId> replaceSeen{nullptr};
|
||||||
|
replace(replaceSeen, ub, ty, builtinTypes->neverType);
|
||||||
|
}
|
||||||
|
emplaceType<BoundType>(asMutable(ty), ub);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -363,4 +401,4 @@ OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypePackId>& seen, TypePack
|
|||||||
return OccursCheckResult::Pass;
|
return OccursCheckResult::Pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace Luau
|
||||||
|
@ -534,7 +534,7 @@ class AstStatBlock : public AstStat
|
|||||||
public:
|
public:
|
||||||
LUAU_RTTI(AstStatBlock)
|
LUAU_RTTI(AstStatBlock)
|
||||||
|
|
||||||
AstStatBlock(const Location& location, const AstArray<AstStat*>& body, bool hasEnd=true);
|
AstStatBlock(const Location& location, const AstArray<AstStat*>& body, bool hasEnd = true);
|
||||||
|
|
||||||
void visit(AstVisitor* visitor) override;
|
void visit(AstVisitor* visitor) override;
|
||||||
|
|
||||||
|
@ -89,13 +89,14 @@ static void reportError(const char* name, const Luau::CompileError& error)
|
|||||||
report(name, error.getLocation(), "CompileError", error.what());
|
report(name, error.getLocation(), "CompileError", error.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string getCodegenAssembly(const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options)
|
static std::string getCodegenAssembly(
|
||||||
|
const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options, Luau::CodeGen::LoweringStats* stats)
|
||||||
{
|
{
|
||||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||||
lua_State* L = globalState.get();
|
lua_State* L = globalState.get();
|
||||||
|
|
||||||
if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0)
|
if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0)
|
||||||
return Luau::CodeGen::getAssembly(L, -1, options);
|
return Luau::CodeGen::getAssembly(L, -1, options, stats);
|
||||||
|
|
||||||
fprintf(stderr, "Error loading bytecode %s\n", name);
|
fprintf(stderr, "Error loading bytecode %s\n", name);
|
||||||
return "";
|
return "";
|
||||||
@ -119,6 +120,8 @@ struct CompileStats
|
|||||||
double parseTime;
|
double parseTime;
|
||||||
double compileTime;
|
double compileTime;
|
||||||
double codegenTime;
|
double codegenTime;
|
||||||
|
|
||||||
|
Luau::CodeGen::LoweringStats lowerStats;
|
||||||
};
|
};
|
||||||
|
|
||||||
static double recordDeltaTime(double& timer)
|
static double recordDeltaTime(double& timer)
|
||||||
@ -213,10 +216,10 @@ static bool compileFile(const char* name, CompileFormat format, Luau::CodeGen::A
|
|||||||
case CompileFormat::CodegenAsm:
|
case CompileFormat::CodegenAsm:
|
||||||
case CompileFormat::CodegenIr:
|
case CompileFormat::CodegenIr:
|
||||||
case CompileFormat::CodegenVerbose:
|
case CompileFormat::CodegenVerbose:
|
||||||
printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options).c_str());
|
printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options, &stats.lowerStats).c_str());
|
||||||
break;
|
break;
|
||||||
case CompileFormat::CodegenNull:
|
case CompileFormat::CodegenNull:
|
||||||
stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options).size();
|
stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options, &stats.lowerStats).size();
|
||||||
stats.codegenTime += recordDeltaTime(currts);
|
stats.codegenTime += recordDeltaTime(currts);
|
||||||
break;
|
break;
|
||||||
case CompileFormat::Null:
|
case CompileFormat::Null:
|
||||||
@ -355,13 +358,22 @@ int main(int argc, char** argv)
|
|||||||
failed += !compileFile(path.c_str(), compileFormat, assemblyTarget, stats);
|
failed += !compileFile(path.c_str(), compileFormat, assemblyTarget, stats);
|
||||||
|
|
||||||
if (compileFormat == CompileFormat::Null)
|
if (compileFormat == CompileFormat::Null)
|
||||||
|
{
|
||||||
printf("Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", int(stats.lines / 1000), int(stats.bytecode / 1024),
|
printf("Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", int(stats.lines / 1000), int(stats.bytecode / 1024),
|
||||||
stats.readTime, stats.parseTime, stats.compileTime);
|
stats.readTime, stats.parseTime, stats.compileTime);
|
||||||
|
}
|
||||||
else if (compileFormat == CompileFormat::CodegenNull)
|
else if (compileFormat == CompileFormat::CodegenNull)
|
||||||
|
{
|
||||||
printf("Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n",
|
printf("Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n",
|
||||||
int(stats.lines / 1000), int(stats.bytecode / 1024), int(stats.codegen / 1024),
|
int(stats.lines / 1000), int(stats.bytecode / 1024), int(stats.codegen / 1024),
|
||||||
stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime,
|
stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime,
|
||||||
stats.codegenTime);
|
stats.codegenTime);
|
||||||
|
|
||||||
|
printf("Lowering stats:\n");
|
||||||
|
printf("- spills to stack: %d, spills to restore: %d, max spill slot %u\n", stats.lowerStats.spillsToSlot, stats.lowerStats.spillsToRestore,
|
||||||
|
stats.lowerStats.maxSpillSlotsUsed);
|
||||||
|
printf("- regalloc failed: %d, lowering failed %d\n", stats.lowerStats.regAllocErrors, stats.lowerStats.loweringErrors);
|
||||||
|
}
|
||||||
|
|
||||||
return failed ? 1 : 0;
|
return failed ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
struct lua_State;
|
struct lua_State;
|
||||||
@ -74,8 +75,18 @@ struct AssemblyOptions
|
|||||||
void* annotatorContext = nullptr;
|
void* annotatorContext = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LoweringStats
|
||||||
|
{
|
||||||
|
int spillsToSlot = 0;
|
||||||
|
int spillsToRestore = 0;
|
||||||
|
unsigned maxSpillSlotsUsed = 0;
|
||||||
|
|
||||||
|
int regAllocErrors = 0;
|
||||||
|
int loweringErrors = 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Generates assembly for target function and all inner functions
|
// Generates assembly for target function and all inner functions
|
||||||
std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {});
|
std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {}, LoweringStats* stats = nullptr);
|
||||||
|
|
||||||
using PerfLogFn = void (*)(void* context, uintptr_t addr, unsigned size, const char* symbol);
|
using PerfLogFn = void (*)(void* context, uintptr_t addr, unsigned size, const char* symbol);
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ namespace Luau
|
|||||||
{
|
{
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
{
|
{
|
||||||
|
|
||||||
|
struct LoweringStats;
|
||||||
|
|
||||||
namespace X64
|
namespace X64
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -33,7 +36,7 @@ struct IrSpillX64
|
|||||||
|
|
||||||
struct IrRegAllocX64
|
struct IrRegAllocX64
|
||||||
{
|
{
|
||||||
IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function);
|
IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function, LoweringStats* stats);
|
||||||
|
|
||||||
RegisterX64 allocReg(SizeX64 size, uint32_t instIdx);
|
RegisterX64 allocReg(SizeX64 size, uint32_t instIdx);
|
||||||
RegisterX64 allocRegOrReuse(SizeX64 size, uint32_t instIdx, std::initializer_list<IrOp> oprefs);
|
RegisterX64 allocRegOrReuse(SizeX64 size, uint32_t instIdx, std::initializer_list<IrOp> oprefs);
|
||||||
@ -70,6 +73,7 @@ struct IrRegAllocX64
|
|||||||
|
|
||||||
AssemblyBuilderX64& build;
|
AssemblyBuilderX64& build;
|
||||||
IrFunction& function;
|
IrFunction& function;
|
||||||
|
LoweringStats* stats = nullptr;
|
||||||
|
|
||||||
uint32_t currInstIdx = ~0u;
|
uint32_t currInstIdx = ~0u;
|
||||||
|
|
||||||
|
@ -264,5 +264,14 @@ uint32_t getNativeContextOffset(int bfid);
|
|||||||
// Cleans up blocks that were created with no users
|
// Cleans up blocks that were created with no users
|
||||||
void killUnusedBlocks(IrFunction& function);
|
void killUnusedBlocks(IrFunction& function);
|
||||||
|
|
||||||
|
// Get blocks in order that tries to maximize fallthrough between them during lowering
|
||||||
|
// We want to mostly preserve build order with fallbacks outlined
|
||||||
|
// But we also use hints from optimization passes that chain blocks together where there's only one out-in edge between them
|
||||||
|
std::vector<uint32_t> getSortedBlockOrder(IrFunction& function);
|
||||||
|
|
||||||
|
// Returns first non-dead block that comes after block at index 'i' in the sorted blocks array
|
||||||
|
// 'dummy' block is returned if the end of array was reached
|
||||||
|
IrBlock& getNextBlock(IrFunction& function, std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i);
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -106,7 +106,7 @@ static std::optional<NativeProto> createNativeFunction(AssemblyBuilder& build, M
|
|||||||
IrBuilder ir;
|
IrBuilder ir;
|
||||||
ir.buildFunctionIr(proto);
|
ir.buildFunctionIr(proto);
|
||||||
|
|
||||||
if (!lowerFunction(ir, build, helpers, proto, {}))
|
if (!lowerFunction(ir, build, helpers, proto, {}, /* stats */ nullptr))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
return createNativeProto(proto, ir);
|
return createNativeProto(proto, ir);
|
||||||
|
@ -43,7 +43,7 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename AssemblyBuilder>
|
template<typename AssemblyBuilder>
|
||||||
static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options)
|
static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, AssemblyOptions options, LoweringStats* stats)
|
||||||
{
|
{
|
||||||
std::vector<Proto*> protos;
|
std::vector<Proto*> protos;
|
||||||
gatherFunctions(protos, clvalue(func)->l.p);
|
gatherFunctions(protos, clvalue(func)->l.p);
|
||||||
@ -66,7 +66,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
|||||||
if (options.includeAssembly || options.includeIr)
|
if (options.includeAssembly || options.includeIr)
|
||||||
logFunctionHeader(build, p);
|
logFunctionHeader(build, p);
|
||||||
|
|
||||||
if (!lowerFunction(ir, build, helpers, p, options))
|
if (!lowerFunction(ir, build, helpers, p, options, stats))
|
||||||
{
|
{
|
||||||
if (build.logText)
|
if (build.logText)
|
||||||
build.logAppend("; skipping (can't lower)\n");
|
build.logAppend("; skipping (can't lower)\n");
|
||||||
@ -90,7 +90,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
|||||||
unsigned int getCpuFeaturesA64();
|
unsigned int getCpuFeaturesA64();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string getAssembly(lua_State* L, int idx, AssemblyOptions options)
|
std::string getAssembly(lua_State* L, int idx, AssemblyOptions options, LoweringStats* stats)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(lua_isLfunction(L, idx));
|
LUAU_ASSERT(lua_isLfunction(L, idx));
|
||||||
const TValue* func = luaA_toobject(L, idx);
|
const TValue* func = luaA_toobject(L, idx);
|
||||||
@ -106,35 +106,35 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options)
|
|||||||
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly);
|
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return getAssemblyImpl(build, func, options);
|
return getAssemblyImpl(build, func, options, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
case AssemblyOptions::A64:
|
case AssemblyOptions::A64:
|
||||||
{
|
{
|
||||||
A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ A64::Feature_JSCVT);
|
A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ A64::Feature_JSCVT);
|
||||||
|
|
||||||
return getAssemblyImpl(build, func, options);
|
return getAssemblyImpl(build, func, options, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
case AssemblyOptions::A64_NoFeatures:
|
case AssemblyOptions::A64_NoFeatures:
|
||||||
{
|
{
|
||||||
A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ 0);
|
A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ 0);
|
||||||
|
|
||||||
return getAssemblyImpl(build, func, options);
|
return getAssemblyImpl(build, func, options, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
case AssemblyOptions::X64_Windows:
|
case AssemblyOptions::X64_Windows:
|
||||||
{
|
{
|
||||||
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::Windows);
|
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::Windows);
|
||||||
|
|
||||||
return getAssemblyImpl(build, func, options);
|
return getAssemblyImpl(build, func, options, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
case AssemblyOptions::X64_SystemV:
|
case AssemblyOptions::X64_SystemV:
|
||||||
{
|
{
|
||||||
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::SystemV);
|
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::SystemV);
|
||||||
|
|
||||||
return getAssemblyImpl(build, func, options);
|
return getAssemblyImpl(build, func, options, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -44,42 +44,10 @@ inline void gatherFunctions(std::vector<Proto*>& results, Proto* proto)
|
|||||||
gatherFunctions(results, proto->p[i]);
|
gatherFunctions(results, proto->p[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline IrBlock& getNextBlock(IrFunction& function, std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i)
|
|
||||||
{
|
|
||||||
for (size_t j = i + 1; j < sortedBlocks.size(); ++j)
|
|
||||||
{
|
|
||||||
IrBlock& block = function.blocks[sortedBlocks[j]];
|
|
||||||
if (block.kind != IrBlockKind::Dead)
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dummy;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename AssemblyBuilder, typename IrLowering>
|
template<typename AssemblyBuilder, typename IrLowering>
|
||||||
inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options)
|
inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options)
|
||||||
{
|
{
|
||||||
// While we will need a better block ordering in the future, right now we want to mostly preserve build order with fallbacks outlined
|
std::vector<uint32_t> sortedBlocks = getSortedBlockOrder(function);
|
||||||
std::vector<uint32_t> sortedBlocks;
|
|
||||||
sortedBlocks.reserve(function.blocks.size());
|
|
||||||
for (uint32_t i = 0; i < function.blocks.size(); i++)
|
|
||||||
sortedBlocks.push_back(i);
|
|
||||||
|
|
||||||
std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) {
|
|
||||||
const IrBlock& a = function.blocks[idxA];
|
|
||||||
const IrBlock& b = function.blocks[idxB];
|
|
||||||
|
|
||||||
// Place fallback blocks at the end
|
|
||||||
if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback))
|
|
||||||
return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback);
|
|
||||||
|
|
||||||
// Try to order by instruction order
|
|
||||||
if (a.sortkey != b.sortkey)
|
|
||||||
return a.sortkey < b.sortkey;
|
|
||||||
|
|
||||||
// Chains of blocks are merged together by having the same sort key and consecutive chain key
|
|
||||||
return a.chainkey < b.chainkey;
|
|
||||||
});
|
|
||||||
|
|
||||||
// For each IR instruction that begins a bytecode instruction, which bytecode instruction is it?
|
// For each IR instruction that begins a bytecode instruction, which bytecode instruction is it?
|
||||||
std::vector<uint32_t> bcLocations(function.instructions.size() + 1, ~0u);
|
std::vector<uint32_t> bcLocations(function.instructions.size() + 1, ~0u);
|
||||||
@ -231,24 +199,26 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool lowerIr(X64::AssemblyBuilderX64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options)
|
inline bool lowerIr(
|
||||||
|
X64::AssemblyBuilderX64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
|
||||||
{
|
{
|
||||||
optimizeMemoryOperandsX64(ir.function);
|
optimizeMemoryOperandsX64(ir.function);
|
||||||
|
|
||||||
X64::IrLoweringX64 lowering(build, helpers, ir.function);
|
X64::IrLoweringX64 lowering(build, helpers, ir.function, stats);
|
||||||
|
|
||||||
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options)
|
inline bool lowerIr(
|
||||||
|
A64::AssemblyBuilderA64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
|
||||||
{
|
{
|
||||||
A64::IrLoweringA64 lowering(build, helpers, ir.function);
|
A64::IrLoweringA64 lowering(build, helpers, ir.function, stats);
|
||||||
|
|
||||||
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename AssemblyBuilder>
|
template<typename AssemblyBuilder>
|
||||||
inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options)
|
inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
|
||||||
{
|
{
|
||||||
killUnusedBlocks(ir.function);
|
killUnusedBlocks(ir.function);
|
||||||
|
|
||||||
@ -264,7 +234,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
|
|||||||
createLinearBlocks(ir, useValueNumbering);
|
createLinearBlocks(ir, useValueNumbering);
|
||||||
}
|
}
|
||||||
|
|
||||||
return lowerIr(build, ir, helpers, proto, options);
|
return lowerIr(build, ir, helpers, proto, options, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
|
@ -385,7 +385,8 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
|||||||
translateInstDupTable(*this, pc, i);
|
translateInstDupTable(*this, pc, i);
|
||||||
break;
|
break;
|
||||||
case LOP_SETLIST:
|
case LOP_SETLIST:
|
||||||
inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]), undef());
|
inst(IrCmd::SETLIST, constUint(i), vmReg(LUAU_INSN_A(*pc)), vmReg(LUAU_INSN_B(*pc)), constInt(LUAU_INSN_C(*pc) - 1), constUint(pc[1]),
|
||||||
|
undef());
|
||||||
break;
|
break;
|
||||||
case LOP_GETUPVAL:
|
case LOP_GETUPVAL:
|
||||||
translateInstGetUpval(*this, pc, i);
|
translateInstGetUpval(*this, pc, i);
|
||||||
|
@ -240,11 +240,12 @@ static bool emitBuiltin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function)
|
IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats)
|
||||||
: build(build)
|
: build(build)
|
||||||
, helpers(helpers)
|
, helpers(helpers)
|
||||||
, function(function)
|
, function(function)
|
||||||
, regs(function, {{x0, x15}, {x16, x17}, {q0, q7}, {q16, q31}})
|
, stats(stats)
|
||||||
|
, regs(function, stats, {{x0, x15}, {x16, x17}, {q0, q7}, {q16, q31}})
|
||||||
, valueTracker(function)
|
, valueTracker(function)
|
||||||
, exitHandlerMap(~0u)
|
, exitHandlerMap(~0u)
|
||||||
{
|
{
|
||||||
@ -2016,6 +2017,15 @@ void IrLoweringA64::finishFunction()
|
|||||||
build.mov(x0, handler.pcpos * sizeof(Instruction));
|
build.mov(x0, handler.pcpos * sizeof(Instruction));
|
||||||
build.b(helpers.updatePcAndContinueInVm);
|
build.b(helpers.updatePcAndContinueInVm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stats)
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
stats->loweringErrors++;
|
||||||
|
|
||||||
|
if (regs.error)
|
||||||
|
stats->regAllocErrors++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IrLoweringA64::hasError() const
|
bool IrLoweringA64::hasError() const
|
||||||
|
@ -17,13 +17,14 @@ namespace CodeGen
|
|||||||
|
|
||||||
struct ModuleHelpers;
|
struct ModuleHelpers;
|
||||||
struct AssemblyOptions;
|
struct AssemblyOptions;
|
||||||
|
struct LoweringStats;
|
||||||
|
|
||||||
namespace A64
|
namespace A64
|
||||||
{
|
{
|
||||||
|
|
||||||
struct IrLoweringA64
|
struct IrLoweringA64
|
||||||
{
|
{
|
||||||
IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function);
|
IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats);
|
||||||
|
|
||||||
void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next);
|
void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next);
|
||||||
void finishBlock(const IrBlock& curr, const IrBlock& next);
|
void finishBlock(const IrBlock& curr, const IrBlock& next);
|
||||||
@ -74,6 +75,7 @@ struct IrLoweringA64
|
|||||||
ModuleHelpers& helpers;
|
ModuleHelpers& helpers;
|
||||||
|
|
||||||
IrFunction& function;
|
IrFunction& function;
|
||||||
|
LoweringStats* stats = nullptr;
|
||||||
|
|
||||||
IrRegAllocA64 regs;
|
IrRegAllocA64 regs;
|
||||||
|
|
||||||
|
@ -22,11 +22,12 @@ namespace CodeGen
|
|||||||
namespace X64
|
namespace X64
|
||||||
{
|
{
|
||||||
|
|
||||||
IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function)
|
IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats)
|
||||||
: build(build)
|
: build(build)
|
||||||
, helpers(helpers)
|
, helpers(helpers)
|
||||||
, function(function)
|
, function(function)
|
||||||
, regs(build, function)
|
, stats(stats)
|
||||||
|
, regs(build, function, stats)
|
||||||
, valueTracker(function)
|
, valueTracker(function)
|
||||||
, exitHandlerMap(~0u)
|
, exitHandlerMap(~0u)
|
||||||
{
|
{
|
||||||
@ -1646,6 +1647,15 @@ void IrLoweringX64::finishFunction()
|
|||||||
build.mov(edx, handler.pcpos * sizeof(Instruction));
|
build.mov(edx, handler.pcpos * sizeof(Instruction));
|
||||||
build.jmp(helpers.updatePcAndContinueInVm);
|
build.jmp(helpers.updatePcAndContinueInVm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stats)
|
||||||
|
{
|
||||||
|
if (regs.maxUsedSlot > kSpillSlots)
|
||||||
|
stats->regAllocErrors++;
|
||||||
|
|
||||||
|
if (regs.maxUsedSlot > stats->maxSpillSlotsUsed)
|
||||||
|
stats->maxSpillSlotsUsed = regs.maxUsedSlot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IrLoweringX64::hasError() const
|
bool IrLoweringX64::hasError() const
|
||||||
|
@ -19,13 +19,14 @@ namespace CodeGen
|
|||||||
|
|
||||||
struct ModuleHelpers;
|
struct ModuleHelpers;
|
||||||
struct AssemblyOptions;
|
struct AssemblyOptions;
|
||||||
|
struct LoweringStats;
|
||||||
|
|
||||||
namespace X64
|
namespace X64
|
||||||
{
|
{
|
||||||
|
|
||||||
struct IrLoweringX64
|
struct IrLoweringX64
|
||||||
{
|
{
|
||||||
IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function);
|
IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats);
|
||||||
|
|
||||||
void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next);
|
void lowerInst(IrInst& inst, uint32_t index, const IrBlock& next);
|
||||||
void finishBlock(const IrBlock& curr, const IrBlock& next);
|
void finishBlock(const IrBlock& curr, const IrBlock& next);
|
||||||
@ -76,6 +77,7 @@ struct IrLoweringX64
|
|||||||
ModuleHelpers& helpers;
|
ModuleHelpers& helpers;
|
||||||
|
|
||||||
IrFunction& function;
|
IrFunction& function;
|
||||||
|
LoweringStats* stats = nullptr;
|
||||||
|
|
||||||
IrRegAllocX64 regs;
|
IrRegAllocX64 regs;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include "IrRegAllocA64.h"
|
#include "IrRegAllocA64.h"
|
||||||
|
|
||||||
#include "Luau/AssemblyBuilderA64.h"
|
#include "Luau/AssemblyBuilderA64.h"
|
||||||
|
#include "Luau/CodeGen.h"
|
||||||
#include "Luau/IrUtils.h"
|
#include "Luau/IrUtils.h"
|
||||||
|
|
||||||
#include "BitUtils.h"
|
#include "BitUtils.h"
|
||||||
@ -109,8 +110,9 @@ static void restoreInst(AssemblyBuilderA64& build, uint32_t& freeSpillSlots, IrF
|
|||||||
inst.regA64 = reg;
|
inst.regA64 = reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
IrRegAllocA64::IrRegAllocA64(IrFunction& function, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs)
|
IrRegAllocA64::IrRegAllocA64(IrFunction& function, LoweringStats* stats, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs)
|
||||||
: function(function)
|
: function(function)
|
||||||
|
, stats(stats)
|
||||||
{
|
{
|
||||||
for (auto& p : regs)
|
for (auto& p : regs)
|
||||||
{
|
{
|
||||||
@ -329,6 +331,9 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init
|
|||||||
spills.push_back(s);
|
spills.push_back(s);
|
||||||
|
|
||||||
def.needsReload = true;
|
def.needsReload = true;
|
||||||
|
|
||||||
|
if (stats)
|
||||||
|
stats->spillsToRestore++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -345,6 +350,14 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init
|
|||||||
spills.push_back(s);
|
spills.push_back(s);
|
||||||
|
|
||||||
def.spilled = true;
|
def.spilled = true;
|
||||||
|
|
||||||
|
if (stats)
|
||||||
|
{
|
||||||
|
stats->spillsToSlot++;
|
||||||
|
|
||||||
|
if (slot != kInvalidSpill && unsigned(slot + 1) > stats->maxSpillSlotsUsed)
|
||||||
|
stats->maxSpillSlotsUsed = slot + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def.regA64 = noreg;
|
def.regA64 = noreg;
|
||||||
|
@ -12,6 +12,9 @@ namespace Luau
|
|||||||
{
|
{
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
{
|
{
|
||||||
|
|
||||||
|
struct LoweringStats;
|
||||||
|
|
||||||
namespace A64
|
namespace A64
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -19,7 +22,7 @@ class AssemblyBuilderA64;
|
|||||||
|
|
||||||
struct IrRegAllocA64
|
struct IrRegAllocA64
|
||||||
{
|
{
|
||||||
IrRegAllocA64(IrFunction& function, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs);
|
IrRegAllocA64(IrFunction& function, LoweringStats* stats, std::initializer_list<std::pair<RegisterA64, RegisterA64>> regs);
|
||||||
|
|
||||||
RegisterA64 allocReg(KindA64 kind, uint32_t index);
|
RegisterA64 allocReg(KindA64 kind, uint32_t index);
|
||||||
RegisterA64 allocTemp(KindA64 kind);
|
RegisterA64 allocTemp(KindA64 kind);
|
||||||
@ -69,6 +72,7 @@ struct IrRegAllocA64
|
|||||||
Set& getSet(KindA64 kind);
|
Set& getSet(KindA64 kind);
|
||||||
|
|
||||||
IrFunction& function;
|
IrFunction& function;
|
||||||
|
LoweringStats* stats = nullptr;
|
||||||
Set gpr, simd;
|
Set gpr, simd;
|
||||||
|
|
||||||
std::vector<Spill> spills;
|
std::vector<Spill> spills;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/IrRegAllocX64.h"
|
#include "Luau/IrRegAllocX64.h"
|
||||||
|
|
||||||
|
#include "Luau/CodeGen.h"
|
||||||
#include "Luau/IrUtils.h"
|
#include "Luau/IrUtils.h"
|
||||||
|
|
||||||
#include "EmitCommonX64.h"
|
#include "EmitCommonX64.h"
|
||||||
@ -14,9 +15,10 @@ namespace X64
|
|||||||
|
|
||||||
static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11};
|
static const RegisterX64 kGprAllocOrder[] = {rax, rdx, rcx, rbx, rsi, rdi, r8, r9, r10, r11};
|
||||||
|
|
||||||
IrRegAllocX64::IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function)
|
IrRegAllocX64::IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function, LoweringStats* stats)
|
||||||
: build(build)
|
: build(build)
|
||||||
, function(function)
|
, function(function)
|
||||||
|
, stats(stats)
|
||||||
, usableXmmRegCount(getXmmRegisterCount(build.abi))
|
, usableXmmRegCount(getXmmRegisterCount(build.abi))
|
||||||
{
|
{
|
||||||
freeGprMap.fill(true);
|
freeGprMap.fill(true);
|
||||||
@ -225,10 +227,16 @@ void IrRegAllocX64::preserve(IrInst& inst)
|
|||||||
|
|
||||||
spill.stackSlot = uint8_t(i);
|
spill.stackSlot = uint8_t(i);
|
||||||
inst.spilled = true;
|
inst.spilled = true;
|
||||||
|
|
||||||
|
if (stats)
|
||||||
|
stats->spillsToSlot++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
inst.needsReload = true;
|
inst.needsReload = true;
|
||||||
|
|
||||||
|
if (stats)
|
||||||
|
stats->spillsToRestore++;
|
||||||
}
|
}
|
||||||
|
|
||||||
spills.push_back(spill);
|
spills.push_back(spill);
|
||||||
|
@ -860,5 +860,43 @@ void killUnusedBlocks(IrFunction& function)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> getSortedBlockOrder(IrFunction& function)
|
||||||
|
{
|
||||||
|
std::vector<uint32_t> sortedBlocks;
|
||||||
|
sortedBlocks.reserve(function.blocks.size());
|
||||||
|
for (uint32_t i = 0; i < function.blocks.size(); i++)
|
||||||
|
sortedBlocks.push_back(i);
|
||||||
|
|
||||||
|
std::sort(sortedBlocks.begin(), sortedBlocks.end(), [&](uint32_t idxA, uint32_t idxB) {
|
||||||
|
const IrBlock& a = function.blocks[idxA];
|
||||||
|
const IrBlock& b = function.blocks[idxB];
|
||||||
|
|
||||||
|
// Place fallback blocks at the end
|
||||||
|
if ((a.kind == IrBlockKind::Fallback) != (b.kind == IrBlockKind::Fallback))
|
||||||
|
return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback);
|
||||||
|
|
||||||
|
// Try to order by instruction order
|
||||||
|
if (a.sortkey != b.sortkey)
|
||||||
|
return a.sortkey < b.sortkey;
|
||||||
|
|
||||||
|
// Chains of blocks are merged together by having the same sort key and consecutive chain key
|
||||||
|
return a.chainkey < b.chainkey;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortedBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrBlock& getNextBlock(IrFunction& function, std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i)
|
||||||
|
{
|
||||||
|
for (size_t j = i + 1; j < sortedBlocks.size(); ++j)
|
||||||
|
{
|
||||||
|
IrBlock& block = function.blocks[sortedBlocks[j]];
|
||||||
|
if (block.kind != IrBlockKind::Dead)
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -14,21 +14,21 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
LUAU_FASTINTVARIABLE(LuauCodeGenBlockSize, 4 * 1024 * 1024)
|
||||||
|
LUAU_FASTINTVARIABLE(LuauCodeGenMaxTotalSize, 256 * 1024 * 1024)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
{
|
{
|
||||||
|
|
||||||
constexpr unsigned kBlockSize = 4 * 1024 * 1024;
|
|
||||||
constexpr unsigned kMaxTotalSize = 256 * 1024 * 1024;
|
|
||||||
|
|
||||||
NativeState::NativeState()
|
NativeState::NativeState()
|
||||||
: NativeState(nullptr, nullptr)
|
: NativeState(nullptr, nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeState::NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext)
|
NativeState::NativeState(AllocationCallback* allocationCallback, void* allocationCallbackContext)
|
||||||
: codeAllocator{kBlockSize, kMaxTotalSize, allocationCallback, allocationCallbackContext}
|
: codeAllocator{size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ inline bool isFlagExperimental(const char* flag)
|
|||||||
static const char* const kList[] = {
|
static const char* const kList[] = {
|
||||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||||
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
||||||
|
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
||||||
// makes sure we always have at least one entry
|
// makes sure we always have at least one entry
|
||||||
nullptr,
|
nullptr,
|
||||||
};
|
};
|
||||||
|
@ -275,15 +275,15 @@ static int math_randomseed(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99,
|
static const unsigned char kPerlinHash[257] = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8,
|
||||||
37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
|
99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87,
|
||||||
20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41,
|
174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92,
|
||||||
55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,
|
41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159,
|
||||||
164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17,
|
86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,
|
||||||
182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110,
|
17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108,
|
||||||
79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14,
|
110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249,
|
||||||
239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24,
|
14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29,
|
||||||
72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151};
|
24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151};
|
||||||
|
|
||||||
const float kPerlinGrad[16][3] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1},
|
const float kPerlinGrad[16][3] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1},
|
||||||
{0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}};
|
{0, -1, 1}, {0, 1, -1}, {0, -1, -1}, {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}};
|
||||||
|
@ -2291,7 +2291,7 @@ reentry:
|
|||||||
{
|
{
|
||||||
// table or userdata with __call, will be called during FORGLOOP
|
// table or userdata with __call, will be called during FORGLOOP
|
||||||
// TODO: we might be able to stop supporting this depending on whether it's used in practice
|
// TODO: we might be able to stop supporting this depending on whether it's used in practice
|
||||||
void (*telemetrycb)(lua_State* L, int gtt, int stt, int itt) = lua_iter_call_telemetry;
|
void (*telemetrycb)(lua_State * L, int gtt, int stt, int itt) = lua_iter_call_telemetry;
|
||||||
|
|
||||||
if (telemetrycb)
|
if (telemetrycb)
|
||||||
telemetrycb(L, ttype(ra), ttype(ra + 1), ttype(ra + 2));
|
telemetrycb(L, ttype(ra), ttype(ra + 1), ttype(ra + 2));
|
||||||
|
@ -3667,8 +3667,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes")
|
|||||||
)");
|
)");
|
||||||
|
|
||||||
StringCompletionCallback callback = [](std::string, std::optional<const ClassType*>,
|
StringCompletionCallback callback = [](std::string, std::optional<const ClassType*>,
|
||||||
std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap>
|
std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap> {
|
||||||
{
|
|
||||||
Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}};
|
Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}};
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
@ -57,8 +57,7 @@ TEST_CASE("CodeAllocationCallbacks")
|
|||||||
|
|
||||||
AllocationData allocationData{};
|
AllocationData allocationData{};
|
||||||
|
|
||||||
const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize)
|
const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize) {
|
||||||
{
|
|
||||||
AllocationData& allocationData = *static_cast<AllocationData*>(context);
|
AllocationData& allocationData = *static_cast<AllocationData*>(context);
|
||||||
if (oldPointer != nullptr)
|
if (oldPointer != nullptr)
|
||||||
{
|
{
|
||||||
|
@ -7371,7 +7371,8 @@ TEST_CASE("BuiltinFoldMathK")
|
|||||||
function test()
|
function test()
|
||||||
return math.pi * 2
|
return math.pi * 2
|
||||||
end
|
end
|
||||||
)", 0, 2),
|
)",
|
||||||
|
0, 2),
|
||||||
R"(
|
R"(
|
||||||
LOADK R0 K0 [6.2831853071795862]
|
LOADK R0 K0 [6.2831853071795862]
|
||||||
RETURN R0 1
|
RETURN R0 1
|
||||||
@ -7382,7 +7383,8 @@ RETURN R0 1
|
|||||||
function test()
|
function test()
|
||||||
return math.pi * 2
|
return math.pi * 2
|
||||||
end
|
end
|
||||||
)", 0, 1),
|
)",
|
||||||
|
0, 1),
|
||||||
R"(
|
R"(
|
||||||
GETIMPORT R1 3 [math.pi]
|
GETIMPORT R1 3 [math.pi]
|
||||||
MULK R0 R1 K0 [2]
|
MULK R0 R1 K0 [2]
|
||||||
@ -7396,7 +7398,8 @@ function test()
|
|||||||
end
|
end
|
||||||
|
|
||||||
math = { pi = 4 }
|
math = { pi = 4 }
|
||||||
)", 0, 2),
|
)",
|
||||||
|
0, 2),
|
||||||
R"(
|
R"(
|
||||||
GETGLOBAL R2 K1 ['math']
|
GETGLOBAL R2 K1 ['math']
|
||||||
GETTABLEKS R1 R2 K2 ['pi']
|
GETTABLEKS R1 R2 K2 ['pi']
|
||||||
|
@ -12,7 +12,7 @@ class IrCallWrapperX64Fixture
|
|||||||
public:
|
public:
|
||||||
IrCallWrapperX64Fixture(ABIX64 abi = ABIX64::Windows)
|
IrCallWrapperX64Fixture(ABIX64 abi = ABIX64::Windows)
|
||||||
: build(/* logText */ true, abi)
|
: build(/* logText */ true, abi)
|
||||||
, regs(build, function)
|
, regs(build, function, nullptr)
|
||||||
, callWrap(regs, build, ~0u)
|
, callWrap(regs, build, ~0u)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class IrRegAllocX64Fixture
|
|||||||
public:
|
public:
|
||||||
IrRegAllocX64Fixture()
|
IrRegAllocX64Fixture()
|
||||||
: build(/* logText */ true, ABIX64::Windows)
|
: build(/* logText */ true, ABIX64::Windows)
|
||||||
, regs(build, function)
|
, regs(build, function, nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,6 +516,71 @@ struct NormalizeFixture : Fixture
|
|||||||
|
|
||||||
TEST_SUITE_BEGIN("Normalize");
|
TEST_SUITE_BEGIN("Normalize");
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NormalizeFixture, "string_intersection_is_commutative")
|
||||||
|
{
|
||||||
|
auto c4 = toString(normal(R"(
|
||||||
|
string & (string & Not<"a"> & Not<"b">)
|
||||||
|
)"));
|
||||||
|
auto c4Reverse = toString(normal(R"(
|
||||||
|
(string & Not<"a"> & Not<"b">) & string
|
||||||
|
)"));
|
||||||
|
CHECK(c4 == c4Reverse);
|
||||||
|
CHECK_EQ("string & ~\"a\" & ~\"b\"", c4);
|
||||||
|
|
||||||
|
auto c5 = toString(normal(R"(
|
||||||
|
(string & Not<"a"> & Not<"b">) & (string & Not<"b"> & Not<"c">)
|
||||||
|
)"));
|
||||||
|
auto c5Reverse = toString(normal(R"(
|
||||||
|
(string & Not<"b"> & Not<"c">) & (string & Not<"a"> & Not<"c">)
|
||||||
|
)"));
|
||||||
|
CHECK(c5 == c5Reverse);
|
||||||
|
CHECK_EQ("string & ~\"a\" & ~\"b\" & ~\"c\"", c5);
|
||||||
|
|
||||||
|
auto c6 = toString(normal(R"(
|
||||||
|
("a" | "b") & (string & Not<"b"> & Not<"c">)
|
||||||
|
)"));
|
||||||
|
auto c6Reverse = toString(normal(R"(
|
||||||
|
(string & Not<"b"> & Not<"c">) & ("a" | "b")
|
||||||
|
)"));
|
||||||
|
CHECK(c6 == c6Reverse);
|
||||||
|
CHECK_EQ("\"a\"", c6);
|
||||||
|
|
||||||
|
auto c7 = toString(normal(R"(
|
||||||
|
string & ("b" | "c")
|
||||||
|
)"));
|
||||||
|
auto c7Reverse = toString(normal(R"(
|
||||||
|
("b" | "c") & string
|
||||||
|
)"));
|
||||||
|
CHECK(c7 == c7Reverse);
|
||||||
|
CHECK_EQ("\"b\" | \"c\"", c7);
|
||||||
|
|
||||||
|
auto c8 = toString(normal(R"(
|
||||||
|
(string & Not<"a"> & Not<"b">) & ("b" | "c")
|
||||||
|
)"));
|
||||||
|
auto c8Reverse = toString(normal(R"(
|
||||||
|
("b" | "c") & (string & Not<"a"> & Not<"b">)
|
||||||
|
)"));
|
||||||
|
CHECK(c8 == c8Reverse);
|
||||||
|
CHECK_EQ("\"c\"", c8);
|
||||||
|
auto c9 = toString(normal(R"(
|
||||||
|
("a" | "b") & ("b" | "c")
|
||||||
|
)"));
|
||||||
|
auto c9Reverse = toString(normal(R"(
|
||||||
|
("b" | "c") & ("a" | "b")
|
||||||
|
)"));
|
||||||
|
CHECK(c9 == c9Reverse);
|
||||||
|
CHECK_EQ("\"b\"", c9);
|
||||||
|
|
||||||
|
auto l = toString(normal(R"(
|
||||||
|
(string | number) & ("a" | true)
|
||||||
|
)"));
|
||||||
|
auto r = toString(normal(R"(
|
||||||
|
("a" | true) & (string | number)
|
||||||
|
)"));
|
||||||
|
CHECK(l == r);
|
||||||
|
CHECK_EQ("\"a\"", l);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "negate_string")
|
TEST_CASE_FIXTURE(NormalizeFixture, "negate_string")
|
||||||
{
|
{
|
||||||
CHECK("number" == toString(normal(R"(
|
CHECK("number" == toString(normal(R"(
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
#include "Luau/Subtyping.h"
|
#include "Luau/Subtyping.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
@ -17,7 +18,15 @@ struct SubtypeFixture : Fixture
|
|||||||
UnifierSharedState sharedState{&ice};
|
UnifierSharedState sharedState{&ice};
|
||||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||||
|
|
||||||
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}};
|
ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)};
|
||||||
|
ScopePtr moduleScope{new Scope(rootScope)};
|
||||||
|
|
||||||
|
Subtyping subtyping = mkSubtyping(rootScope);
|
||||||
|
|
||||||
|
Subtyping mkSubtyping(const ScopePtr& scope)
|
||||||
|
{
|
||||||
|
return Subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}, NotNull{scope.get()}};
|
||||||
|
}
|
||||||
|
|
||||||
TypePackId pack(std::initializer_list<TypeId> tys)
|
TypePackId pack(std::initializer_list<TypeId> tys)
|
||||||
{
|
{
|
||||||
@ -66,11 +75,18 @@ struct SubtypeFixture : Fixture
|
|||||||
return arena.addType(UnionType{{a, b}});
|
return arena.addType(UnionType{{a, b}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `~`
|
||||||
TypeId negate(TypeId ty)
|
TypeId negate(TypeId ty)
|
||||||
{
|
{
|
||||||
return arena.addType(NegationType{ty});
|
return arena.addType(NegationType{ty});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "literal"
|
||||||
|
TypeId str(const char* literal)
|
||||||
|
{
|
||||||
|
return arena.addType(SingletonType{StringSingleton{literal}});
|
||||||
|
}
|
||||||
|
|
||||||
TypeId cls(const std::string& name, std::optional<TypeId> parent = std::nullopt)
|
TypeId cls(const std::string& name, std::optional<TypeId> parent = std::nullopt)
|
||||||
{
|
{
|
||||||
return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, ""});
|
return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, ""});
|
||||||
@ -97,8 +113,8 @@ struct SubtypeFixture : Fixture
|
|||||||
return arena.addType(MetatableType{tbl(std::move(tableProps)), tbl(std::move(metaProps))});
|
return arena.addType(MetatableType{tbl(std::move(tableProps)), tbl(std::move(metaProps))});
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId genericT = arena.addType(GenericType{"T"});
|
TypeId genericT = arena.addType(GenericType{moduleScope.get(), "T"});
|
||||||
TypeId genericU = arena.addType(GenericType{"U"});
|
TypeId genericU = arena.addType(GenericType{moduleScope.get(), "U"});
|
||||||
|
|
||||||
TypePackId genericAs = arena.addTypePack(GenericTypePack{"A"});
|
TypePackId genericAs = arena.addTypePack(GenericTypePack{"A"});
|
||||||
TypePackId genericBs = arena.addTypePack(GenericTypePack{"B"});
|
TypePackId genericBs = arena.addTypePack(GenericTypePack{"B"});
|
||||||
@ -113,6 +129,10 @@ struct SubtypeFixture : Fixture
|
|||||||
TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}});
|
TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}});
|
||||||
TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}});
|
TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}});
|
||||||
|
|
||||||
|
TypeId aType = arena.addType(SingletonType{StringSingleton{"a"}});
|
||||||
|
TypeId bType = arena.addType(SingletonType{StringSingleton{"b"}});
|
||||||
|
TypeId trueSingleton = arena.addType(SingletonType{BooleanSingleton{true}});
|
||||||
|
TypeId falseSingleton = arena.addType(SingletonType{BooleanSingleton{false}});
|
||||||
TypeId helloOrWorldType = join(helloType, worldType);
|
TypeId helloOrWorldType = join(helloType, worldType);
|
||||||
TypeId trueOrFalseType = join(builtinTypes->trueType, builtinTypes->falseType);
|
TypeId trueOrFalseType = join(builtinTypes->trueType, builtinTypes->falseType);
|
||||||
|
|
||||||
@ -149,160 +169,76 @@ struct SubtypeFixture : Fixture
|
|||||||
const TypeId nothingToNothingType = fn({}, {});
|
const TypeId nothingToNothingType = fn({}, {});
|
||||||
|
|
||||||
// (number) -> string
|
// (number) -> string
|
||||||
const TypeId numberToStringType = fn(
|
const TypeId numberToStringType = fn({builtinTypes->numberType}, {builtinTypes->stringType});
|
||||||
{builtinTypes->numberType},
|
|
||||||
{builtinTypes->stringType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (unknown) -> string
|
// (unknown) -> string
|
||||||
const TypeId unknownToStringType = fn(
|
const TypeId unknownToStringType = fn({builtinTypes->unknownType}, {builtinTypes->stringType});
|
||||||
{builtinTypes->unknownType},
|
|
||||||
{builtinTypes->stringType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (number) -> ()
|
// (number) -> ()
|
||||||
const TypeId numberToNothingType = fn(
|
const TypeId numberToNothingType = fn({builtinTypes->numberType}, {});
|
||||||
{builtinTypes->numberType},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
// () -> number
|
// () -> number
|
||||||
const TypeId nothingToNumberType = fn(
|
const TypeId nothingToNumberType = fn({}, {builtinTypes->numberType});
|
||||||
{},
|
|
||||||
{builtinTypes->numberType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (number) -> number
|
// (number) -> number
|
||||||
const TypeId numberToNumberType = fn(
|
const TypeId numberToNumberType = fn({builtinTypes->numberType}, {builtinTypes->numberType});
|
||||||
{builtinTypes->numberType},
|
|
||||||
{builtinTypes->numberType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (number) -> unknown
|
// (number) -> unknown
|
||||||
const TypeId numberToUnknownType = fn(
|
const TypeId numberToUnknownType = fn({builtinTypes->numberType}, {builtinTypes->unknownType});
|
||||||
{builtinTypes->numberType},
|
|
||||||
{builtinTypes->unknownType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (number) -> (string, string)
|
// (number) -> (string, string)
|
||||||
const TypeId numberToTwoStringsType = fn(
|
const TypeId numberToTwoStringsType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->stringType});
|
||||||
{builtinTypes->numberType},
|
|
||||||
{builtinTypes->stringType, builtinTypes->stringType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (number) -> (string, unknown)
|
// (number) -> (string, unknown)
|
||||||
const TypeId numberToStringAndUnknownType = fn(
|
const TypeId numberToStringAndUnknownType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->unknownType});
|
||||||
{builtinTypes->numberType},
|
|
||||||
{builtinTypes->stringType, builtinTypes->unknownType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (number, number) -> string
|
// (number, number) -> string
|
||||||
const TypeId numberNumberToStringType = fn(
|
const TypeId numberNumberToStringType = fn({builtinTypes->numberType, builtinTypes->numberType}, {builtinTypes->stringType});
|
||||||
{builtinTypes->numberType, builtinTypes->numberType},
|
|
||||||
{builtinTypes->stringType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (unknown, number) -> string
|
// (unknown, number) -> string
|
||||||
const TypeId unknownNumberToStringType = fn(
|
const TypeId unknownNumberToStringType = fn({builtinTypes->unknownType, builtinTypes->numberType}, {builtinTypes->stringType});
|
||||||
{builtinTypes->unknownType, builtinTypes->numberType},
|
|
||||||
{builtinTypes->stringType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (number, string) -> string
|
// (number, string) -> string
|
||||||
const TypeId numberAndStringToStringType = fn(
|
const TypeId numberAndStringToStringType = fn({builtinTypes->numberType, builtinTypes->stringType}, {builtinTypes->stringType});
|
||||||
{builtinTypes->numberType, builtinTypes->stringType},
|
|
||||||
{builtinTypes->stringType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (number, ...string) -> string
|
// (number, ...string) -> string
|
||||||
const TypeId numberAndStringsToStringType = fn(
|
const TypeId numberAndStringsToStringType =
|
||||||
{builtinTypes->numberType}, VariadicTypePack{builtinTypes->stringType},
|
fn({builtinTypes->numberType}, VariadicTypePack{builtinTypes->stringType}, {builtinTypes->stringType});
|
||||||
{builtinTypes->stringType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (number, ...string?) -> string
|
// (number, ...string?) -> string
|
||||||
const TypeId numberAndOptionalStringsToStringType = fn(
|
const TypeId numberAndOptionalStringsToStringType =
|
||||||
{builtinTypes->numberType}, VariadicTypePack{builtinTypes->optionalStringType},
|
fn({builtinTypes->numberType}, VariadicTypePack{builtinTypes->optionalStringType}, {builtinTypes->stringType});
|
||||||
{builtinTypes->stringType}
|
|
||||||
);
|
|
||||||
|
|
||||||
// (...number) -> number
|
// (...number) -> number
|
||||||
const TypeId numbersToNumberType = arena.addType(FunctionType{
|
const TypeId numbersToNumberType =
|
||||||
arena.addTypePack(VariadicTypePack{builtinTypes->numberType}),
|
arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->numberType}), arena.addTypePack({builtinTypes->numberType})});
|
||||||
arena.addTypePack({builtinTypes->numberType})
|
|
||||||
});
|
|
||||||
|
|
||||||
// <T>(T) -> ()
|
// <T>(T) -> ()
|
||||||
const TypeId genericTToNothingType = arena.addType(FunctionType{
|
const TypeId genericTToNothingType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({genericT}), builtinTypes->emptyTypePack});
|
||||||
{genericT},
|
|
||||||
{},
|
|
||||||
arena.addTypePack({genericT}),
|
|
||||||
builtinTypes->emptyTypePack
|
|
||||||
});
|
|
||||||
|
|
||||||
// <T>(T) -> T
|
// <T>(T) -> T
|
||||||
const TypeId genericTToTType = arena.addType(FunctionType{
|
const TypeId genericTToTType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({genericT}), arena.addTypePack({genericT})});
|
||||||
{genericT},
|
|
||||||
{},
|
|
||||||
arena.addTypePack({genericT}),
|
|
||||||
arena.addTypePack({genericT})
|
|
||||||
});
|
|
||||||
|
|
||||||
// <U>(U) -> ()
|
// <U>(U) -> ()
|
||||||
const TypeId genericUToNothingType = arena.addType(FunctionType{
|
const TypeId genericUToNothingType = arena.addType(FunctionType{{genericU}, {}, arena.addTypePack({genericU}), builtinTypes->emptyTypePack});
|
||||||
{genericU},
|
|
||||||
{},
|
|
||||||
arena.addTypePack({genericU}),
|
|
||||||
builtinTypes->emptyTypePack
|
|
||||||
});
|
|
||||||
|
|
||||||
// <T>() -> T
|
// <T>() -> T
|
||||||
const TypeId genericNothingToTType = arena.addType(FunctionType{
|
const TypeId genericNothingToTType = arena.addType(FunctionType{{genericT}, {}, builtinTypes->emptyTypePack, arena.addTypePack({genericT})});
|
||||||
{genericT},
|
|
||||||
{},
|
|
||||||
builtinTypes->emptyTypePack,
|
|
||||||
arena.addTypePack({genericT})
|
|
||||||
});
|
|
||||||
|
|
||||||
// <A...>(A...) -> A...
|
// <A...>(A...) -> A...
|
||||||
const TypeId genericAsToAsType = arena.addType(FunctionType{
|
const TypeId genericAsToAsType = arena.addType(FunctionType{{}, {genericAs}, genericAs, genericAs});
|
||||||
{},
|
|
||||||
{genericAs},
|
|
||||||
genericAs,
|
|
||||||
genericAs
|
|
||||||
});
|
|
||||||
|
|
||||||
// <A...>(A...) -> number
|
// <A...>(A...) -> number
|
||||||
const TypeId genericAsToNumberType = arena.addType(FunctionType{
|
const TypeId genericAsToNumberType = arena.addType(FunctionType{{}, {genericAs}, genericAs, arena.addTypePack({builtinTypes->numberType})});
|
||||||
{},
|
|
||||||
{genericAs},
|
|
||||||
genericAs,
|
|
||||||
arena.addTypePack({builtinTypes->numberType})
|
|
||||||
});
|
|
||||||
|
|
||||||
// <B...>(B...) -> B...
|
// <B...>(B...) -> B...
|
||||||
const TypeId genericBsToBsType = arena.addType(FunctionType{
|
const TypeId genericBsToBsType = arena.addType(FunctionType{{}, {genericBs}, genericBs, genericBs});
|
||||||
{},
|
|
||||||
{genericBs},
|
|
||||||
genericBs,
|
|
||||||
genericBs
|
|
||||||
});
|
|
||||||
|
|
||||||
// <B..., C...>(B...) -> C...
|
// <B..., C...>(B...) -> C...
|
||||||
const TypeId genericBsToCsType = arena.addType(FunctionType{
|
const TypeId genericBsToCsType = arena.addType(FunctionType{{}, {genericBs, genericCs}, genericBs, genericCs});
|
||||||
{},
|
|
||||||
{genericBs, genericCs},
|
|
||||||
genericBs,
|
|
||||||
genericCs
|
|
||||||
});
|
|
||||||
|
|
||||||
// <A...>() -> A...
|
// <A...>() -> A...
|
||||||
const TypeId genericNothingToAsType = arena.addType(FunctionType{
|
const TypeId genericNothingToAsType = arena.addType(FunctionType{{}, {genericAs}, builtinTypes->emptyTypePack, genericAs});
|
||||||
{},
|
|
||||||
{genericAs},
|
|
||||||
builtinTypes->emptyTypePack,
|
|
||||||
genericAs
|
|
||||||
});
|
|
||||||
|
|
||||||
// { lower : string -> string }
|
// { lower : string -> string }
|
||||||
TypeId tableWithLower = tbl(TableType::Props{{"lower", fn({builtinTypes->stringType}, {builtinTypes->stringType})}});
|
TypeId tableWithLower = tbl(TableType::Props{{"lower", fn({builtinTypes->stringType}, {builtinTypes->stringType})}});
|
||||||
@ -728,60 +664,98 @@ TEST_CASE_FIXTURE(SubtypeFixture, "{x: number?} <!: {x: number}")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "{x: <T>(T) -> ()} <: {x: <U>(U) -> ()}")
|
TEST_CASE_FIXTURE(SubtypeFixture, "{x: <T>(T) -> ()} <: {x: <U>(U) -> ()}")
|
||||||
{
|
{
|
||||||
CHECK_IS_SUBTYPE(
|
CHECK_IS_SUBTYPE(tbl({{"x", genericTToNothingType}}), tbl({{"x", genericUToNothingType}}));
|
||||||
tbl({{"x", genericTToNothingType}}),
|
|
||||||
tbl({{"x", genericUToNothingType}})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }")
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }")
|
||||||
{
|
{
|
||||||
CHECK_IS_SUBTYPE(
|
CHECK_IS_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({}));
|
||||||
meta({{"x", builtinTypes->numberType}}),
|
|
||||||
meta({})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { @metatable { x: boolean } }")
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { @metatable { x: boolean } }")
|
||||||
{
|
{
|
||||||
CHECK_IS_NOT_SUBTYPE(
|
CHECK_IS_NOT_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({{"x", builtinTypes->booleanType}}));
|
||||||
meta({{"x", builtinTypes->numberType}}),
|
|
||||||
meta({{"x", builtinTypes->booleanType}})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <!: { @metatable { x: boolean } }")
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <!: { @metatable { x: boolean } }")
|
||||||
{
|
{
|
||||||
CHECK_IS_NOT_SUBTYPE(
|
CHECK_IS_NOT_SUBTYPE(meta({}), meta({{"x", builtinTypes->booleanType}}));
|
||||||
meta({}),
|
|
||||||
meta({{"x", builtinTypes->booleanType}})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <: {}")
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <: {}")
|
||||||
{
|
{
|
||||||
CHECK_IS_SUBTYPE(
|
CHECK_IS_SUBTYPE(meta({}), tbl({}));
|
||||||
meta({}),
|
|
||||||
tbl({})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { u: boolean }, x: number } <: { x: number }")
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { u: boolean }, x: number } <: { x: number }")
|
||||||
{
|
{
|
||||||
CHECK_IS_SUBTYPE(
|
CHECK_IS_SUBTYPE(meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->numberType}}));
|
||||||
meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}),
|
|
||||||
tbl({{"x", builtinTypes->numberType}})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { x: number }")
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { x: number }")
|
||||||
{
|
{
|
||||||
CHECK_IS_NOT_SUBTYPE(
|
CHECK_IS_NOT_SUBTYPE(meta({{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->numberType}}));
|
||||||
meta({{"x", builtinTypes->numberType}}),
|
|
||||||
tbl({{"x", builtinTypes->numberType}})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Negated subtypes
|
||||||
|
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->neverType), builtinTypes->stringType);
|
||||||
|
TEST_IS_SUBTYPE(negate(builtinTypes->unknownType), builtinTypes->stringType);
|
||||||
|
TEST_IS_NOT_SUBTYPE(negate(builtinTypes->anyType), builtinTypes->stringType);
|
||||||
|
TEST_IS_SUBTYPE(negate(meet(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
||||||
|
TEST_IS_NOT_SUBTYPE(negate(join(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
||||||
|
|
||||||
|
// Negated supertypes: never/unknown/any/error
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->neverType));
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->neverType, negate(builtinTypes->unknownType));
|
||||||
|
TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->unknownType));
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->numberType, negate(builtinTypes->anyType));
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->anyType));
|
||||||
|
|
||||||
|
// Negated supertypes: unions
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->booleanType, negate(join(builtinTypes->stringType, builtinTypes->numberType)));
|
||||||
|
TEST_IS_SUBTYPE(rootClass, negate(join(childClass, builtinTypes->numberType)));
|
||||||
|
TEST_IS_SUBTYPE(str("foo"), negate(join(builtinTypes->numberType, builtinTypes->booleanType)));
|
||||||
|
TEST_IS_NOT_SUBTYPE(str("foo"), negate(join(builtinTypes->stringType, builtinTypes->numberType)));
|
||||||
|
TEST_IS_NOT_SUBTYPE(childClass, negate(join(rootClass, builtinTypes->numberType)));
|
||||||
|
TEST_IS_NOT_SUBTYPE(numbersToNumberType, negate(join(builtinTypes->functionType, rootClass)));
|
||||||
|
|
||||||
|
// Negated supertypes: intersections
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->booleanType, negate(meet(builtinTypes->stringType, str("foo"))));
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->trueType, negate(meet(builtinTypes->booleanType, builtinTypes->numberType)));
|
||||||
|
TEST_IS_SUBTYPE(rootClass, negate(meet(builtinTypes->classType, childClass)));
|
||||||
|
TEST_IS_SUBTYPE(childClass, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
|
||||||
|
TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(meet(builtinTypes->classType, builtinTypes->numberType)));
|
||||||
|
TEST_IS_NOT_SUBTYPE(str("foo"), negate(meet(builtinTypes->stringType, negate(str("bar")))));
|
||||||
|
|
||||||
|
// Negated supertypes: tables and metatables
|
||||||
|
TEST_IS_SUBTYPE(tbl({}), negate(builtinTypes->numberType));
|
||||||
|
TEST_IS_NOT_SUBTYPE(tbl({}), negate(builtinTypes->tableType));
|
||||||
|
TEST_IS_SUBTYPE(meta({}), negate(builtinTypes->numberType));
|
||||||
|
TEST_IS_NOT_SUBTYPE(meta({}), negate(builtinTypes->tableType));
|
||||||
|
|
||||||
|
// Negated supertypes: Functions
|
||||||
|
TEST_IS_SUBTYPE(numberToNumberType, negate(builtinTypes->classType));
|
||||||
|
TEST_IS_NOT_SUBTYPE(numberToNumberType, negate(builtinTypes->functionType));
|
||||||
|
|
||||||
|
// Negated supertypes: Primitives and singletons
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->numberType));
|
||||||
|
TEST_IS_SUBTYPE(str("foo"), meet(builtinTypes->stringType, negate(str("bar"))));
|
||||||
|
TEST_IS_NOT_SUBTYPE(builtinTypes->trueType, negate(builtinTypes->booleanType));
|
||||||
|
TEST_IS_NOT_SUBTYPE(str("foo"), negate(str("foo")));
|
||||||
|
TEST_IS_NOT_SUBTYPE(str("foo"), negate(builtinTypes->stringType));
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->falseType, negate(builtinTypes->trueType));
|
||||||
|
TEST_IS_SUBTYPE(builtinTypes->falseType, meet(builtinTypes->booleanType, negate(builtinTypes->trueType)));
|
||||||
|
TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, meet(builtinTypes->booleanType, negate(builtinTypes->trueType)));
|
||||||
|
TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, negate(str("foo")));
|
||||||
|
TEST_IS_NOT_SUBTYPE(builtinTypes->booleanType, negate(builtinTypes->falseType));
|
||||||
|
|
||||||
|
// Negated supertypes: Classes
|
||||||
|
TEST_IS_SUBTYPE(rootClass, negate(builtinTypes->tableType));
|
||||||
|
TEST_IS_NOT_SUBTYPE(rootClass, negate(builtinTypes->classType));
|
||||||
|
TEST_IS_NOT_SUBTYPE(childClass, negate(rootClass));
|
||||||
|
TEST_IS_NOT_SUBTYPE(childClass, meet(builtinTypes->classType, negate(rootClass)));
|
||||||
|
TEST_IS_SUBTYPE(anotherChildClass, meet(builtinTypes->classType, negate(childClass)));
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class")
|
TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class")
|
||||||
{
|
{
|
||||||
CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType);
|
CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType);
|
||||||
@ -829,13 +803,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~GrandchildOne <!: number")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}")
|
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}")
|
||||||
{
|
{
|
||||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt)
|
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||||
{
|
|
||||||
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
||||||
});
|
});
|
||||||
|
|
||||||
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt)
|
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||||
{
|
|
||||||
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -844,13 +816,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 wh
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 where t2 = {trim: (t2) -> t2}")
|
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 where t2 = {trim: (t2) -> t2}")
|
||||||
{
|
{
|
||||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt)
|
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||||
{
|
|
||||||
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
||||||
});
|
});
|
||||||
|
|
||||||
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt)
|
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||||
{
|
|
||||||
tt->props["trim"] = fn({ty}, {ty});
|
tt->props["trim"] = fn({ty}, {ty});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -859,13 +829,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 w
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} <!: t2 where t2 = {trim: (t2) -> string}")
|
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} <!: t2 where t2 = {trim: (t2) -> string}")
|
||||||
{
|
{
|
||||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt)
|
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||||
{
|
|
||||||
tt->props["trim"] = fn({ty}, {ty});
|
tt->props["trim"] = fn({ty}, {ty});
|
||||||
});
|
});
|
||||||
|
|
||||||
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt)
|
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||||
{
|
|
||||||
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -960,6 +928,50 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(string) -> number <: ~fun & (string) -> numb
|
|||||||
CHECK_IS_NOT_SUBTYPE(numberToStringType, meet(negate(builtinTypes->functionType), numberToStringType));
|
CHECK_IS_NOT_SUBTYPE(numberToStringType, meet(negate(builtinTypes->functionType), numberToStringType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "~\"a\" & ~\"b\" & string <: { lower : (string) -> ()}")
|
||||||
|
{
|
||||||
|
CHECK_IS_SUBTYPE(meet(meet(negate(aType), negate(bType)), builtinTypes->stringType), tableWithLower);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "\"a\" | (~\"b\" & string) <: { lower : (string) -> ()}")
|
||||||
|
{
|
||||||
|
CHECK_IS_SUBTYPE(join(aType, meet(negate(bType), builtinTypes->stringType)), tableWithLower);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "(string | number) & (\"a\" | true) <: { lower: (string) -> string }")
|
||||||
|
{
|
||||||
|
auto base = meet(join(builtinTypes->stringType, builtinTypes->numberType), join(aType, trueSingleton));
|
||||||
|
CHECK_IS_SUBTYPE(base, tableWithLower);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Within the scope to which a generic belongs, that generic ought to be treated
|
||||||
|
* as its bounds.
|
||||||
|
*
|
||||||
|
* We do not yet support bounded generics, so all generics are considered to be
|
||||||
|
* bounded by unknown.
|
||||||
|
*/
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "unknown <: X")
|
||||||
|
{
|
||||||
|
ScopePtr childScope{new Scope(rootScope)};
|
||||||
|
ScopePtr grandChildScope{new Scope(childScope)};
|
||||||
|
|
||||||
|
TypeId genericX = arena.addType(GenericType(childScope.get(), "X"));
|
||||||
|
|
||||||
|
SubtypingResult usingGlobalScope = subtyping.isSubtype(builtinTypes->unknownType, genericX);
|
||||||
|
CHECK_MESSAGE(!usingGlobalScope.isSubtype, "Expected " << builtinTypes->unknownType << " </: " << genericX);
|
||||||
|
|
||||||
|
Subtyping childSubtyping{mkSubtyping(childScope)};
|
||||||
|
|
||||||
|
SubtypingResult usingChildScope = childSubtyping.isSubtype(builtinTypes->unknownType, genericX);
|
||||||
|
CHECK_MESSAGE(usingChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX);
|
||||||
|
|
||||||
|
Subtyping grandChildSubtyping{mkSubtyping(grandChildScope)};
|
||||||
|
|
||||||
|
SubtypingResult usingGrandChildScope = grandChildSubtyping.isSubtype(builtinTypes->unknownType, genericX);
|
||||||
|
CHECK_MESSAGE(usingGrandChildScope.isSubtype, "Expected " << builtinTypes->unknownType << " <: " << genericX);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* <A>(A) -> A <: <X>(X) -> X
|
* <A>(A) -> A <: <X>(X) -> X
|
||||||
* A can be bound to X.
|
* A can be bound to X.
|
||||||
|
@ -1067,8 +1067,9 @@ local w = c and 1
|
|||||||
CHECK("false | number" == toString(requireType("z")));
|
CHECK("false | number" == toString(requireType("z")));
|
||||||
else
|
else
|
||||||
CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean
|
CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
CHECK("((false?) & a) | number" == toString(requireType("w")));
|
CHECK("((false?) & unknown) | number" == toString(requireType("w")));
|
||||||
else
|
else
|
||||||
CHECK("(boolean | number)?" == toString(requireType("w")));
|
CHECK("(boolean | number)?" == toString(requireType("w")));
|
||||||
}
|
}
|
||||||
|
@ -308,7 +308,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position")
|
|||||||
else
|
else
|
||||||
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
|
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
|
||||||
CHECK_EQ("number", toString(requireTypeAtPosition({6, 26})));
|
CHECK_EQ("number", toString(requireTypeAtPosition({6, 26})));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
|
||||||
|
@ -3788,4 +3788,40 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_shifted_tables")
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"LuauFixIndexerSubtypingOrdering", true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Thing = { name: string, prop: boolean }
|
||||||
|
|
||||||
|
local arrayOfThings : {Thing} = {
|
||||||
|
{ name = "a" }
|
||||||
|
}
|
||||||
|
|
||||||
|
local dictOfThings : {[string]: Thing} = {
|
||||||
|
a = { name = "a" }
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
|
|
||||||
|
TypeError& err1 = result.errors[0];
|
||||||
|
MissingProperties* error1 = get<MissingProperties>(err1);
|
||||||
|
REQUIRE(error1);
|
||||||
|
REQUIRE(error1->properties.size() == 1);
|
||||||
|
|
||||||
|
CHECK_EQ("prop", error1->properties[0]);
|
||||||
|
|
||||||
|
TypeError& err2 = result.errors[1];
|
||||||
|
TypeMismatch* mismatch = get<TypeMismatch>(err2);
|
||||||
|
REQUIRE(mismatch);
|
||||||
|
MissingProperties* error2 = get<MissingProperties>(*mismatch->error);
|
||||||
|
REQUIRE(error2);
|
||||||
|
REQUIRE(error2->properties.size() == 1);
|
||||||
|
|
||||||
|
CHECK_EQ("prop", error2->properties[0]);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -1428,7 +1428,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "be_sure_to_use_active_txnlog_when_evaluating
|
|||||||
|
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
|
||||||
for (const auto& e: result.errors)
|
for (const auto& e : result.errors)
|
||||||
CHECK(5 == e.location.begin.line);
|
CHECK(5 == e.location.begin.line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,18 +81,12 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
|
TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
|
||||||
{
|
{
|
||||||
TypeId stringToUnit = arena.addType(FunctionType{
|
TypeId stringToUnit = arena.addType(FunctionType{arena.addTypePack({builtinTypes.stringType}), arena.addTypePack({})});
|
||||||
arena.addTypePack({builtinTypes.stringType}),
|
|
||||||
arena.addTypePack({})
|
|
||||||
});
|
|
||||||
|
|
||||||
auto [x, xFree] = freshType();
|
auto [x, xFree] = freshType();
|
||||||
TypePackId y = arena.freshTypePack(&scope);
|
TypePackId y = arena.freshTypePack(&scope);
|
||||||
|
|
||||||
TypeId xToY = arena.addType(FunctionType{
|
TypeId xToY = arena.addType(FunctionType{arena.addTypePack({x}), y});
|
||||||
arena.addTypePack({x}),
|
|
||||||
y
|
|
||||||
});
|
|
||||||
|
|
||||||
u2.unify(stringToUnit, xToY);
|
u2.unify(stringToUnit, xToY);
|
||||||
|
|
||||||
@ -105,4 +99,54 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
|
|||||||
CHECK(!yPack->tail);
|
CHECK(!yPack->tail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Unifier2Fixture, "generalize_a_type_that_is_bounded_by_another_generalizable_type")
|
||||||
|
{
|
||||||
|
auto [t1, ft1] = freshType();
|
||||||
|
auto [t2, ft2] = freshType();
|
||||||
|
|
||||||
|
// t2 <: t1 <: unknown
|
||||||
|
// unknown <: t2 <: t1
|
||||||
|
|
||||||
|
ft1->lowerBound = t2;
|
||||||
|
ft2->upperBound = t1;
|
||||||
|
ft2->lowerBound = builtinTypes.unknownType;
|
||||||
|
|
||||||
|
auto t2generalized = u2.generalize(NotNull{&scope}, t2);
|
||||||
|
REQUIRE(t2generalized);
|
||||||
|
|
||||||
|
CHECK(follow(t1) == follow(t2));
|
||||||
|
|
||||||
|
auto t1generalized = u2.generalize(NotNull{&scope}, t1);
|
||||||
|
REQUIRE(t1generalized);
|
||||||
|
|
||||||
|
CHECK(builtinTypes.unknownType == follow(t1));
|
||||||
|
CHECK(builtinTypes.unknownType == follow(t2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as generalize_a_type_that_is_bounded_by_another_generalizable_type
|
||||||
|
// except that we generalize the types in the opposite order
|
||||||
|
TEST_CASE_FIXTURE(Unifier2Fixture, "generalize_a_type_that_is_bounded_by_another_generalizable_type_in_reverse_order")
|
||||||
|
{
|
||||||
|
auto [t1, ft1] = freshType();
|
||||||
|
auto [t2, ft2] = freshType();
|
||||||
|
|
||||||
|
// t2 <: t1 <: unknown
|
||||||
|
// unknown <: t2 <: t1
|
||||||
|
|
||||||
|
ft1->lowerBound = t2;
|
||||||
|
ft2->upperBound = t1;
|
||||||
|
ft2->lowerBound = builtinTypes.unknownType;
|
||||||
|
|
||||||
|
auto t1generalized = u2.generalize(NotNull{&scope}, t1);
|
||||||
|
REQUIRE(t1generalized);
|
||||||
|
|
||||||
|
CHECK(follow(t1) == follow(t2));
|
||||||
|
|
||||||
|
auto t2generalized = u2.generalize(NotNull{&scope}, t2);
|
||||||
|
REQUIRE(t2generalized);
|
||||||
|
|
||||||
|
CHECK(builtinTypes.unknownType == follow(t1));
|
||||||
|
CHECK(builtinTypes.unknownType == follow(t2));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -3,7 +3,6 @@ AnnotationTests.two_type_params
|
|||||||
AstQuery.last_argument_function_call_type
|
AstQuery.last_argument_function_call_type
|
||||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||||
AutocompleteTest.autocomplete_if_else_regression
|
|
||||||
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||||
AutocompleteTest.autocomplete_oop_implicit_self
|
AutocompleteTest.autocomplete_oop_implicit_self
|
||||||
AutocompleteTest.autocomplete_response_perf1
|
AutocompleteTest.autocomplete_response_perf1
|
||||||
@ -92,8 +91,6 @@ IntersectionTypes.table_intersection_write_sealed_indirect
|
|||||||
IntersectionTypes.table_write_sealed_indirect
|
IntersectionTypes.table_write_sealed_indirect
|
||||||
Normalize.negations_of_tables
|
Normalize.negations_of_tables
|
||||||
Normalize.specific_functions_cannot_be_negated
|
Normalize.specific_functions_cannot_be_negated
|
||||||
ParserTests.parse_nesting_based_end_detection
|
|
||||||
ParserTests.parse_nesting_based_end_detection_single_line
|
|
||||||
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illegal
|
||||||
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
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.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
||||||
@ -124,13 +121,13 @@ RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
|||||||
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
|
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
|
||||||
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||||
TableTests.call_method
|
TableTests.call_method
|
||||||
TableTests.call_method_with_explicit_self_argument
|
|
||||||
TableTests.cannot_augment_sealed_table
|
TableTests.cannot_augment_sealed_table
|
||||||
TableTests.cannot_change_type_of_unsealed_table_prop
|
TableTests.cannot_change_type_of_unsealed_table_prop
|
||||||
TableTests.casting_sealed_tables_with_props_into_table_with_indexer
|
TableTests.casting_sealed_tables_with_props_into_table_with_indexer
|
||||||
TableTests.casting_tables_with_props_into_table_with_indexer4
|
TableTests.casting_tables_with_props_into_table_with_indexer4
|
||||||
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
||||||
TableTests.checked_prop_too_early
|
TableTests.checked_prop_too_early
|
||||||
|
TableTests.cli_84607_missing_prop_in_array_or_dict
|
||||||
TableTests.cyclic_shifted_tables
|
TableTests.cyclic_shifted_tables
|
||||||
TableTests.defining_a_method_for_a_local_sealed_table_must_fail
|
TableTests.defining_a_method_for_a_local_sealed_table_must_fail
|
||||||
TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail
|
TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail
|
||||||
@ -139,6 +136,7 @@ TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
|||||||
TableTests.dont_extend_unsealed_tables_in_rvalue_position
|
TableTests.dont_extend_unsealed_tables_in_rvalue_position
|
||||||
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
||||||
TableTests.dont_leak_free_table_props
|
TableTests.dont_leak_free_table_props
|
||||||
|
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
||||||
TableTests.dont_suggest_exact_match_keys
|
TableTests.dont_suggest_exact_match_keys
|
||||||
TableTests.error_detailed_metatable_prop
|
TableTests.error_detailed_metatable_prop
|
||||||
TableTests.explicitly_typed_table
|
TableTests.explicitly_typed_table
|
||||||
@ -154,19 +152,21 @@ TableTests.inequality_operators_imply_exactly_matching_types
|
|||||||
TableTests.infer_array_2
|
TableTests.infer_array_2
|
||||||
TableTests.infer_indexer_from_value_property_in_literal
|
TableTests.infer_indexer_from_value_property_in_literal
|
||||||
TableTests.infer_type_when_indexing_from_a_table_indexer
|
TableTests.infer_type_when_indexing_from_a_table_indexer
|
||||||
TableTests.inferred_properties_of_a_table_should_start_with_the_same_TypeLevel_of_that_table
|
|
||||||
TableTests.inferred_return_type_of_free_table
|
TableTests.inferred_return_type_of_free_table
|
||||||
TableTests.instantiate_table_cloning_3
|
TableTests.instantiate_table_cloning_3
|
||||||
TableTests.leaking_bad_metatable_errors
|
TableTests.leaking_bad_metatable_errors
|
||||||
TableTests.less_exponential_blowup_please
|
TableTests.less_exponential_blowup_please
|
||||||
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
||||||
TableTests.mixed_tables_with_implicit_numbered_keys
|
TableTests.mixed_tables_with_implicit_numbered_keys
|
||||||
|
TableTests.ok_to_add_property_to_free_table
|
||||||
TableTests.ok_to_provide_a_subtype_during_construction
|
TableTests.ok_to_provide_a_subtype_during_construction
|
||||||
TableTests.okay_to_add_property_to_unsealed_tables_by_assignment
|
TableTests.okay_to_add_property_to_unsealed_tables_by_assignment
|
||||||
TableTests.okay_to_add_property_to_unsealed_tables_by_function_call
|
|
||||||
TableTests.only_ascribe_synthetic_names_at_module_scope
|
|
||||||
TableTests.oop_indexer_works
|
TableTests.oop_indexer_works
|
||||||
TableTests.oop_polymorphic
|
TableTests.oop_polymorphic
|
||||||
|
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.pass_incompatible_union_to_a_generic_table_without_crashing
|
||||||
|
TableTests.passing_compatible_unions_to_a_generic_table_without_crashing
|
||||||
TableTests.quantify_even_that_table_was_never_exported_at_all
|
TableTests.quantify_even_that_table_was_never_exported_at_all
|
||||||
TableTests.quantify_metatables_of_metatables_of_table
|
TableTests.quantify_metatables_of_metatables_of_table
|
||||||
TableTests.quantifying_a_bound_var_works
|
TableTests.quantifying_a_bound_var_works
|
||||||
@ -185,7 +185,6 @@ TableTests.table_unification_4
|
|||||||
TableTests.type_mismatch_on_massive_table_is_cut_short
|
TableTests.type_mismatch_on_massive_table_is_cut_short
|
||||||
TableTests.used_colon_instead_of_dot
|
TableTests.used_colon_instead_of_dot
|
||||||
TableTests.used_dot_instead_of_colon
|
TableTests.used_dot_instead_of_colon
|
||||||
TableTests.used_dot_instead_of_colon_but_correctly
|
|
||||||
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
|
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
|
||||||
TableTests.wrong_assign_does_hit_indexer
|
TableTests.wrong_assign_does_hit_indexer
|
||||||
ToDot.function
|
ToDot.function
|
||||||
@ -215,6 +214,7 @@ TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
|||||||
TypeFamilyTests.family_as_fn_arg
|
TypeFamilyTests.family_as_fn_arg
|
||||||
TypeFamilyTests.table_internal_families
|
TypeFamilyTests.table_internal_families
|
||||||
TypeFamilyTests.unsolvable_family
|
TypeFamilyTests.unsolvable_family
|
||||||
|
TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
|
||||||
TypeInfer.bidirectional_checking_of_higher_order_function
|
TypeInfer.bidirectional_checking_of_higher_order_function
|
||||||
TypeInfer.check_type_infer_recursion_count
|
TypeInfer.check_type_infer_recursion_count
|
||||||
TypeInfer.cli_39932_use_unifier_in_ensure_methods
|
TypeInfer.cli_39932_use_unifier_in_ensure_methods
|
||||||
@ -226,21 +226,24 @@ TypeInfer.fuzz_free_table_type_change_during_index_check
|
|||||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||||
TypeInfer.infer_locals_via_assignment_from_its_call_site
|
TypeInfer.infer_locals_via_assignment_from_its_call_site
|
||||||
TypeInfer.no_stack_overflow_from_isoptional
|
TypeInfer.no_stack_overflow_from_isoptional
|
||||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
|
|
||||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
||||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||||
TypeInfer.type_infer_cache_limit_normalizer
|
TypeInfer.type_infer_cache_limit_normalizer
|
||||||
TypeInfer.type_infer_recursion_limit_no_ice
|
TypeInfer.type_infer_recursion_limit_no_ice
|
||||||
TypeInfer.type_infer_recursion_limit_normalizer
|
TypeInfer.type_infer_recursion_limit_normalizer
|
||||||
TypeInferAnyError.can_subscript_any
|
TypeInferAnyError.can_subscript_any
|
||||||
|
TypeInferAnyError.for_in_loop_iterator_is_any
|
||||||
TypeInferAnyError.for_in_loop_iterator_is_any2
|
TypeInferAnyError.for_in_loop_iterator_is_any2
|
||||||
|
TypeInferAnyError.for_in_loop_iterator_is_error
|
||||||
|
TypeInferAnyError.for_in_loop_iterator_is_error2
|
||||||
TypeInferAnyError.for_in_loop_iterator_returns_any
|
TypeInferAnyError.for_in_loop_iterator_returns_any
|
||||||
|
TypeInferAnyError.for_in_loop_iterator_returns_any2
|
||||||
TypeInferAnyError.intersection_of_any_can_have_props
|
TypeInferAnyError.intersection_of_any_can_have_props
|
||||||
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
||||||
TypeInferAnyError.union_of_types_regression_test
|
TypeInferAnyError.union_of_types_regression_test
|
||||||
TypeInferClasses.can_read_prop_of_base_class_using_string
|
|
||||||
TypeInferClasses.class_type_mismatch_with_name_conflict
|
TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||||
TypeInferClasses.index_instance_property
|
TypeInferClasses.index_instance_property
|
||||||
|
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
||||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
||||||
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
||||||
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
|
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
|
||||||
@ -254,10 +257,10 @@ TypeInferFunctions.higher_order_function_2
|
|||||||
TypeInferFunctions.higher_order_function_4
|
TypeInferFunctions.higher_order_function_4
|
||||||
TypeInferFunctions.improved_function_arg_mismatch_errors
|
TypeInferFunctions.improved_function_arg_mismatch_errors
|
||||||
TypeInferFunctions.infer_anonymous_function_arguments
|
TypeInferFunctions.infer_anonymous_function_arguments
|
||||||
|
TypeInferFunctions.infer_anonymous_function_arguments_outside_call
|
||||||
TypeInferFunctions.infer_generic_function_function_argument
|
TypeInferFunctions.infer_generic_function_function_argument
|
||||||
TypeInferFunctions.infer_generic_function_function_argument_overloaded
|
TypeInferFunctions.infer_generic_function_function_argument_overloaded
|
||||||
TypeInferFunctions.infer_generic_lib_function_function_argument
|
TypeInferFunctions.infer_generic_lib_function_function_argument
|
||||||
TypeInferFunctions.infer_anonymous_function_arguments_outside_call
|
|
||||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||||
TypeInferFunctions.luau_subtyping_is_np_hard
|
TypeInferFunctions.luau_subtyping_is_np_hard
|
||||||
TypeInferFunctions.no_lossy_function_type
|
TypeInferFunctions.no_lossy_function_type
|
||||||
@ -269,21 +272,22 @@ TypeInferFunctions.too_few_arguments_variadic_generic2
|
|||||||
TypeInferFunctions.too_many_arguments_error_location
|
TypeInferFunctions.too_many_arguments_error_location
|
||||||
TypeInferFunctions.too_many_return_values_in_parentheses
|
TypeInferFunctions.too_many_return_values_in_parentheses
|
||||||
TypeInferFunctions.too_many_return_values_no_function
|
TypeInferFunctions.too_many_return_values_no_function
|
||||||
TypeInferFunctions.toposort_doesnt_break_mutual_recursion
|
|
||||||
TypeInferFunctions.vararg_function_is_quantified
|
|
||||||
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
|
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
|
||||||
TypeInferLoops.dcr_iteration_on_never_gives_never
|
TypeInferLoops.dcr_iteration_on_never_gives_never
|
||||||
TypeInferLoops.for_in_loop
|
TypeInferLoops.for_in_loop
|
||||||
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
||||||
|
TypeInferLoops.for_in_loop_on_error
|
||||||
TypeInferLoops.for_in_loop_with_custom_iterator
|
TypeInferLoops.for_in_loop_with_custom_iterator
|
||||||
TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator
|
TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator
|
||||||
TypeInferLoops.for_in_loop_with_next
|
TypeInferLoops.for_in_loop_with_next
|
||||||
TypeInferLoops.ipairs_produces_integral_indices
|
TypeInferLoops.ipairs_produces_integral_indices
|
||||||
TypeInferLoops.iteration_regression_issue_69967_alt
|
TypeInferLoops.iteration_regression_issue_69967_alt
|
||||||
|
TypeInferLoops.loop_iter_basic
|
||||||
TypeInferLoops.loop_iter_metamethod_nil
|
TypeInferLoops.loop_iter_metamethod_nil
|
||||||
TypeInferLoops.loop_iter_metamethod_ok_with_inference
|
TypeInferLoops.loop_iter_metamethod_ok_with_inference
|
||||||
TypeInferLoops.loop_iter_trailing_nil
|
TypeInferLoops.loop_iter_trailing_nil
|
||||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||||
|
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||||
TypeInferModules.do_not_modify_imported_types_5
|
TypeInferModules.do_not_modify_imported_types_5
|
||||||
TypeInferModules.module_type_conflict
|
TypeInferModules.module_type_conflict
|
||||||
TypeInferModules.module_type_conflict_instantiated
|
TypeInferModules.module_type_conflict_instantiated
|
||||||
@ -294,7 +298,6 @@ TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
|||||||
TypeInferOOP.methods_are_topologically_sorted
|
TypeInferOOP.methods_are_topologically_sorted
|
||||||
TypeInferOperators.and_binexps_dont_unify
|
TypeInferOperators.and_binexps_dont_unify
|
||||||
TypeInferOperators.cli_38355_recursive_union
|
TypeInferOperators.cli_38355_recursive_union
|
||||||
TypeInferOperators.compound_assign_mismatch_metatable
|
|
||||||
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
||||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||||
TypeInferOperators.luau_polyfill_is_array
|
TypeInferOperators.luau_polyfill_is_array
|
||||||
|
Loading…
Reference in New Issue
Block a user