mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15: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;
|
||||
|
||||
ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
DcrLogger* logger, NotNull<DataFlowGraph> dfg, std::vector<RequireCycle> requireCycles);
|
||||
ConstraintGraphBuilder(ModulePtr module, NotNull<Normalizer> normalizer, NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope,
|
||||
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.
|
||||
|
@ -23,4 +23,4 @@ struct GlobalTypes
|
||||
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.
|
||||
struct ReplaceGenerics : Substitution
|
||||
{
|
||||
ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope, const std::vector<TypeId>& generics,
|
||||
const std::vector<TypePackId>& genericPacks)
|
||||
ReplaceGenerics(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope,
|
||||
const std::vector<TypeId>& generics, const std::vector<TypePackId>& genericPacks)
|
||||
: Substitution(log, arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, level(level)
|
||||
@ -77,6 +77,7 @@ struct Instantiation : Substitution
|
||||
* Instantiation fails only when processing the type causes internal recursion
|
||||
* 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
|
||||
|
@ -19,20 +19,18 @@ class TypeIds;
|
||||
class Normalizer;
|
||||
struct NormalizedType;
|
||||
struct NormalizedClassType;
|
||||
struct NormalizedStringType;
|
||||
struct NormalizedFunctionType;
|
||||
|
||||
|
||||
struct SubtypingResult
|
||||
{
|
||||
// Did the test succeed?
|
||||
bool isSubtype = false;
|
||||
bool isErrorSuppressing = false;
|
||||
bool normalizationTooComplex = false;
|
||||
|
||||
// If so, what constraints are implied by this relation?
|
||||
// If not, what happened?
|
||||
|
||||
void andAlso(const SubtypingResult& other);
|
||||
void orElse(const SubtypingResult& other);
|
||||
SubtypingResult& andAlso(const SubtypingResult& other);
|
||||
SubtypingResult& orElse(const SubtypingResult& other);
|
||||
|
||||
// Only negates the `isSubtype`.
|
||||
static SubtypingResult negate(const SubtypingResult& result);
|
||||
@ -47,6 +45,8 @@ struct Subtyping
|
||||
NotNull<Normalizer> normalizer;
|
||||
NotNull<InternalErrorReporter> iceReporter;
|
||||
|
||||
NotNull<Scope> scope;
|
||||
|
||||
enum class Variance
|
||||
{
|
||||
Covariant,
|
||||
@ -72,6 +72,12 @@ struct Subtyping
|
||||
|
||||
SeenSet seenTypes;
|
||||
|
||||
Subtyping(const Subtyping&) = delete;
|
||||
Subtyping& operator=(const Subtyping&) = delete;
|
||||
|
||||
Subtyping(Subtyping&&) = default;
|
||||
Subtyping& operator=(Subtyping&&) = default;
|
||||
|
||||
// TODO cache
|
||||
// TODO cyclic types
|
||||
// TODO recursion limits
|
||||
@ -80,43 +86,61 @@ struct Subtyping
|
||||
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy);
|
||||
|
||||
private:
|
||||
SubtypingResult isSubtype_(TypeId subTy, TypeId superTy);
|
||||
SubtypingResult isSubtype_(TypePackId subTy, TypePackId superTy);
|
||||
SubtypingResult isCovariantWith(TypeId subTy, TypeId superTy);
|
||||
SubtypingResult isCovariantWith(TypePackId subTy, TypePackId 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);
|
||||
SubtypingResult isSubtype_(const UnionType* subUnion, TypeId 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);
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult isInvariantWith(SubTy&& subTy, SuperTy&& superTy);
|
||||
|
||||
SubtypingResult isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm);
|
||||
SubtypingResult isSubtype_(const NormalizedClassType& subClass, const NormalizedClassType& superClass, const TypeIds& superTables);
|
||||
SubtypingResult isSubtype_(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction);
|
||||
SubtypingResult isSubtype_(const TypeIds& subTypes, const TypeIds& superTypes);
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult isCovariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||
|
||||
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(TypePackId subTp, TypePackId superTp);
|
||||
|
||||
template <typename T, typename Container>
|
||||
template<typename T, typename Container>
|
||||
TypeId makeAggregateType(const Container& container, TypeId orElse);
|
||||
|
||||
[[noreturn]]
|
||||
void unexpected(TypePackId tp);
|
||||
[[noreturn]] void unexpected(TypePackId tp);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -849,6 +849,18 @@ bool isSubclass(const ClassType* cls, const ClassType* parent);
|
||||
|
||||
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>
|
||||
const T* get(TypeId tv)
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ struct DcrLogger;
|
||||
struct TypeCheckLimits;
|
||||
struct UnifierSharedState;
|
||||
|
||||
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule& sourceModule,
|
||||
Module* module);
|
||||
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
|
||||
const SourceModule& sourceModule, Module* module);
|
||||
|
||||
} // 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>`
|
||||
// and cooperates with C++'s `if (auto p = ...)` syntax without the extra fatness of `std::optional`.
|
||||
template<typename A, typename B>
|
||||
struct TryPair {
|
||||
struct TryPair
|
||||
{
|
||||
A first;
|
||||
B second;
|
||||
|
||||
|
@ -105,10 +105,12 @@ struct Unifier
|
||||
* 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.
|
||||
*/
|
||||
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:
|
||||
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);
|
||||
|
||||
// Traverse the two types provided and block on any BlockedTypes we find.
|
||||
|
@ -55,8 +55,8 @@ struct Unifier2
|
||||
bool unify(TypePackId subTp, TypePackId superTp);
|
||||
|
||||
std::optional<TypeId> generalize(NotNull<Scope> scope, TypeId ty);
|
||||
private:
|
||||
|
||||
private:
|
||||
/**
|
||||
* @returns simplify(left | right)
|
||||
*/
|
||||
@ -72,4 +72,4 @@ private:
|
||||
OccursCheckResult occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauBoundLazyTypes2)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
|
||||
namespace Luau
|
||||
@ -220,7 +221,21 @@ struct GenericTypeVisitor
|
||||
traverse(btv->boundTo);
|
||||
}
|
||||
else if (auto ftv = get<FreeType>(ty))
|
||||
visit(ty, *ftv);
|
||||
{
|
||||
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);
|
||||
}
|
||||
else if (auto gtv = get<GenericType>(ty))
|
||||
visit(ty, *gtv);
|
||||
else if (auto etv = get<ErrorType>(ty))
|
||||
|
@ -282,20 +282,8 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
||||
ParenthesesRecommendation parens =
|
||||
indexType == PropIndexType::Key ? ParenthesesRecommendation::None : getParenRecommendation(type, nodes, typeCorrect);
|
||||
|
||||
result[name] = AutocompleteEntry{
|
||||
AutocompleteEntryKind::Property,
|
||||
type,
|
||||
prop.deprecated,
|
||||
isWrongIndexer(type),
|
||||
typeCorrect,
|
||||
containingClass,
|
||||
&prop,
|
||||
prop.documentationSymbol,
|
||||
{},
|
||||
parens,
|
||||
{},
|
||||
indexType == PropIndexType::Colon
|
||||
};
|
||||
result[name] = AutocompleteEntry{AutocompleteEntryKind::Property, 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 {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
static std::optional<std::string> tryToStringDetailed(const ScopePtr& scope, T ty, bool functionTypeArguments)
|
||||
{
|
||||
ToStringOptions opts;
|
||||
@ -1418,7 +1406,7 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func
|
||||
name = "a" + std::to_string(argIdx);
|
||||
|
||||
if (std::optional<Name> type = tryGetTypeNameInScope(scope, args[argIdx], true))
|
||||
result += name + ": " + *type;
|
||||
result += name + ": " + *type;
|
||||
else
|
||||
result += name;
|
||||
}
|
||||
@ -1434,7 +1422,7 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func
|
||||
if (std::optional<std::string> res = tryToStringDetailed(scope, pack->ty, true))
|
||||
varArgType = std::move(res);
|
||||
}
|
||||
|
||||
|
||||
if (varArgType)
|
||||
result += "...: " + *varArgType;
|
||||
else
|
||||
@ -1461,7 +1449,8 @@ static std::string makeAnonymous(const ScopePtr& scope, const FunctionType& func
|
||||
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>();
|
||||
if (!call && ancestry.size() > 1)
|
||||
@ -1498,10 +1487,10 @@ static std::optional<AutocompleteEntry> makeAnonymousAutofilled(const ModulePtr&
|
||||
auto [args, tail] = flatten(outerFunction->argTypes);
|
||||
if (argument < args.size())
|
||||
argType = args[argument];
|
||||
|
||||
|
||||
if (!argType)
|
||||
return std::nullopt;
|
||||
|
||||
|
||||
TypeId followed = follow(*argType);
|
||||
const FunctionType* type = get<FunctionType>(followed);
|
||||
if (!type)
|
||||
|
@ -780,7 +780,7 @@ void TypeCloner::operator()(const UnionType& t)
|
||||
// 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
|
||||
// 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;
|
||||
|
||||
std::vector<TypeId> options;
|
||||
|
@ -420,17 +420,17 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
|
||||
{
|
||||
switch (shouldSuppressErrors(normalizer, ty))
|
||||
{
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
break;
|
||||
case ErrorSuppression::Suppress:
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
ty = simplifyUnion(builtinTypes, arena, ty, builtinTypes->errorType).result;
|
||||
break;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
reportError(location, NormalizationTooComplex{});
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
break;
|
||||
case ErrorSuppression::DoNotSuppress:
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
break;
|
||||
case ErrorSuppression::Suppress:
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
ty = simplifyUnion(builtinTypes, arena, ty, builtinTypes->errorType).result;
|
||||
break;
|
||||
case ErrorSuppression::NormalizationFailed:
|
||||
reportError(location, NormalizationTooComplex{});
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -430,6 +430,35 @@ bool ConstraintSolver::isDone()
|
||||
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()
|
||||
{
|
||||
Anyification a{arena, rootScope, builtinTypes, &iceReporter, builtinTypes->anyType, builtinTypes->anyTypePack};
|
||||
@ -446,12 +475,28 @@ void ConstraintSolver::finalizeModule()
|
||||
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, NotNull{&iceReporter}};
|
||||
|
||||
std::deque<TypeAndLocation> queue;
|
||||
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);
|
||||
if (generalizedTy)
|
||||
binding.typeId = *generalizedTy;
|
||||
else
|
||||
TypeAndLocation binding = queue.front();
|
||||
queue.pop_front();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
UnifierSharedState sharedState{&iceReporter};
|
||||
Unifier u{normalizer, scope, Location{}, Covariant};
|
||||
u.enableNewSolver();
|
||||
Unifier2 u{arena, builtinTypes, NotNull{&iceReporter}};
|
||||
|
||||
u.tryUnify(subPack, superPack);
|
||||
u.unify(subPack, superPack);
|
||||
|
||||
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
||||
unblock(subPack, Location{});
|
||||
unblock(superPack, Location{});
|
||||
|
||||
u.log.commit();
|
||||
|
||||
unblock(changedTypes, Location{});
|
||||
unblock(changedPacks, Location{});
|
||||
|
||||
return std::move(u.errors);
|
||||
return {};
|
||||
}
|
||||
|
||||
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:
|
||||
{
|
||||
checkNonMissingPropertyLeavesHaveNulloptTableProperty();
|
||||
return pathStr + conditionalNewline
|
||||
+ "has type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
||||
return pathStr + conditionalNewline + "has type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty);
|
||||
}
|
||||
case DiffError::Kind::MissingTableProperty:
|
||||
{
|
||||
@ -127,17 +125,14 @@ std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLea
|
||||
{
|
||||
if (!leaf.tableProperty.has_value())
|
||||
throw InternalCompilerError{"leaf.tableProperty is nullopt"};
|
||||
return pathStr + "." + *leaf.tableProperty + conditionalNewline
|
||||
+ "has type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
||||
return pathStr + "." + *leaf.tableProperty + conditionalNewline + "has type" + conditionalNewline + conditionalIndent +
|
||||
Luau::toString(*leaf.ty);
|
||||
}
|
||||
else if (otherLeaf.ty.has_value())
|
||||
{
|
||||
if (!otherLeaf.tableProperty.has_value())
|
||||
throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"};
|
||||
return pathStr + conditionalNewline
|
||||
+ "is missing the property" + conditionalNewline
|
||||
+ conditionalIndent + *otherLeaf.tableProperty;
|
||||
return pathStr + conditionalNewline + "is missing the property" + conditionalNewline + conditionalIndent + *otherLeaf.tableProperty;
|
||||
}
|
||||
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())
|
||||
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
||||
return pathStr + conditionalNewline
|
||||
+ "is a union containing type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
||||
return pathStr + conditionalNewline + "is a union containing type" + conditionalNewline + conditionalIndent + Luau::toString(*leaf.ty);
|
||||
}
|
||||
else if (otherLeaf.ty.has_value())
|
||||
{
|
||||
return pathStr + conditionalNewline
|
||||
+ "is a union missing type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*otherLeaf.ty);
|
||||
return pathStr + conditionalNewline + "is a union missing type" + conditionalNewline + conditionalIndent + Luau::toString(*otherLeaf.ty);
|
||||
}
|
||||
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())
|
||||
throw InternalCompilerError{"leaf.unionIndex is nullopt"};
|
||||
return pathStr + conditionalNewline
|
||||
+ "is an intersection containing type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*leaf.ty);
|
||||
return pathStr + conditionalNewline + "is an intersection containing type" + conditionalNewline + conditionalIndent +
|
||||
Luau::toString(*leaf.ty);
|
||||
}
|
||||
else if (otherLeaf.ty.has_value())
|
||||
{
|
||||
return pathStr + conditionalNewline
|
||||
+ "is an intersection missing type" + conditionalNewline
|
||||
+ conditionalIndent + Luau::toString(*otherLeaf.ty);
|
||||
return pathStr + conditionalNewline + "is an intersection missing type" + conditionalNewline + conditionalIndent +
|
||||
Luau::toString(*otherLeaf.ty);
|
||||
}
|
||||
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())
|
||||
throw InternalCompilerError{"leaf.minLength is nullopt"};
|
||||
return pathStr + conditionalNewline
|
||||
+ "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments";
|
||||
return pathStr + conditionalNewline + "takes " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " arguments";
|
||||
}
|
||||
case DiffError::Kind::LengthMismatchInFnRets:
|
||||
{
|
||||
if (!leaf.minLength.has_value())
|
||||
throw InternalCompilerError{"leaf.minLength is nullopt"};
|
||||
return pathStr + conditionalNewline
|
||||
+ "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values";
|
||||
return pathStr + conditionalNewline + "returns " + std::to_string(*leaf.minLength) + (leaf.isVariadic ? " or more" : "") + " values";
|
||||
}
|
||||
default:
|
||||
{
|
||||
@ -249,17 +236,15 @@ std::string DiffError::toString(bool multiLine) const
|
||||
case DiffError::Kind::IncompatibleGeneric:
|
||||
{
|
||||
std::string diffPathStr{diffPath.toString(true)};
|
||||
return "DiffError: these two types are not equal because the left generic at" + conditionalNewline
|
||||
+ conditionalIndent + leftRootName + diffPathStr + conditionalNewline
|
||||
+ "cannot be the same type parameter as the right generic at" + conditionalNewline
|
||||
+ conditionalIndent + rightRootName + diffPathStr;
|
||||
return "DiffError: these two types are not equal because the left generic at" + conditionalNewline + conditionalIndent + leftRootName +
|
||||
diffPathStr + conditionalNewline + "cannot be the same type parameter as the right generic at" + conditionalNewline +
|
||||
conditionalIndent + rightRootName + diffPathStr;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return "DiffError: these two types are not equal because the left type at" + conditionalNewline
|
||||
+ conditionalIndent + toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline +
|
||||
"while the right type at" + conditionalNewline
|
||||
+ conditionalIndent + toStringALeaf(rightRootName, right, left, multiLine);
|
||||
return "DiffError: these two types are not equal because the left type at" + conditionalNewline + conditionalIndent +
|
||||
toStringALeaf(leftRootName, left, right, multiLine) + "," + conditionalNewline + "while the right type at" + conditionalNewline +
|
||||
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 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();
|
||||
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);
|
||||
|
||||
|
@ -2791,8 +2791,8 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
|
||||
else if (first == "native")
|
||||
{
|
||||
if (space != std::string::npos)
|
||||
emitWarning(context, LintWarning::Code_CommentDirective, hc.location,
|
||||
"native directive has extra symbols at the end of the line");
|
||||
emitWarning(
|
||||
context, LintWarning::Code_CommentDirective, hc.location, "native directive has extra symbols at the end of the line");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -176,7 +176,7 @@ const NormalizedStringType NormalizedStringType::never;
|
||||
|
||||
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)
|
||||
{
|
||||
@ -1983,18 +1983,68 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th
|
||||
|
||||
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())
|
||||
return;
|
||||
if (here.isString())
|
||||
here.resetToNever();
|
||||
|
||||
for (auto it = here.singletons.begin(); it != here.singletons.end();)
|
||||
// Case 4, Case 7
|
||||
else if (here.isString())
|
||||
{
|
||||
if (there.singletons.count(it->first))
|
||||
it++;
|
||||
else
|
||||
it = here.singletons.erase(it);
|
||||
here.singletons.clear();
|
||||
for (const auto& [key, type] : there.singletons)
|
||||
here.singletons[key] = type;
|
||||
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)
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
@ -28,12 +29,12 @@ struct VarianceFlipper
|
||||
{
|
||||
switch (oldValue)
|
||||
{
|
||||
case Subtyping::Variance::Covariant:
|
||||
*variance = Subtyping::Variance::Contravariant;
|
||||
break;
|
||||
case Subtyping::Variance::Contravariant:
|
||||
*variance = Subtyping::Variance::Covariant;
|
||||
break;
|
||||
case Subtyping::Variance::Covariant:
|
||||
*variance = Subtyping::Variance::Contravariant;
|
||||
break;
|
||||
case Subtyping::Variance::Contravariant:
|
||||
*variance = Subtyping::Variance::Covariant;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,19 +44,21 @@ struct VarianceFlipper
|
||||
}
|
||||
};
|
||||
|
||||
void SubtypingResult::andAlso(const SubtypingResult& other)
|
||||
SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other)
|
||||
{
|
||||
isSubtype &= other.isSubtype;
|
||||
// `|=` is intentional here, we want to preserve error related flags.
|
||||
isErrorSuppressing |= other.isErrorSuppressing;
|
||||
normalizationTooComplex |= other.normalizationTooComplex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SubtypingResult::orElse(const SubtypingResult& other)
|
||||
SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other)
|
||||
{
|
||||
isSubtype |= other.isSubtype;
|
||||
isErrorSuppressing |= other.isErrorSuppressing;
|
||||
normalizationTooComplex |= other.normalizationTooComplex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
|
||||
@ -88,9 +91,9 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
||||
mappedGenerics.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& ub = bounds.upperBound;
|
||||
@ -98,7 +101,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
||||
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
|
||||
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
|
||||
|
||||
result.andAlso(isSubtype_(lowerBound, upperBound));
|
||||
result.andAlso(isCovariantWith(lowerBound, upperBound));
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -106,7 +109,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
||||
|
||||
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
return isSubtype_(subTp, superTp);
|
||||
return isCovariantWith(subTp, superTp);
|
||||
}
|
||||
|
||||
namespace
|
||||
@ -119,16 +122,17 @@ struct SeenSetPopper
|
||||
SeenSetPopper(Subtyping::SeenSet* seenTypes, std::pair<TypeId, TypeId> pair)
|
||||
: seenTypes(seenTypes)
|
||||
, pair(pair)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
~SeenSetPopper()
|
||||
{
|
||||
seenTypes->erase(pair);
|
||||
}
|
||||
};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
||||
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
subTy = follow(subTy);
|
||||
superTy = follow(superTy);
|
||||
@ -146,19 +150,27 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
||||
|
||||
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))
|
||||
return isSubtype_(subUnion, superTy);
|
||||
return isCovariantWith(subUnion, superTy);
|
||||
else if (auto superUnion = get<UnionType>(superTy))
|
||||
return isSubtype_(subTy, superUnion);
|
||||
return isCovariantWith(subTy, superUnion);
|
||||
else if (auto superIntersection = get<IntersectionType>(superTy))
|
||||
return isSubtype_(subTy, superIntersection);
|
||||
return isCovariantWith(subTy, superIntersection);
|
||||
else if (auto subIntersection = get<IntersectionType>(subTy))
|
||||
{
|
||||
SubtypingResult result = isSubtype_(subIntersection, superTy);
|
||||
SubtypingResult result = isCovariantWith(subIntersection, superTy);
|
||||
if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex)
|
||||
return result;
|
||||
else
|
||||
return isSubtype_(normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
return isCovariantWith(normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
}
|
||||
else if (get<AnyType>(superTy))
|
||||
return {true}; // This is always true.
|
||||
@ -166,14 +178,12 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
// any = unknown | error, so we rewrite this to match.
|
||||
// As per TAPL: A | B <: T iff A <: T && B <: T
|
||||
SubtypingResult result = isSubtype_(builtinTypes->unknownType, superTy);
|
||||
result.andAlso(isSubtype_(builtinTypes->errorType, superTy));
|
||||
return result;
|
||||
return isCovariantWith(builtinTypes->unknownType, superTy).andAlso(isCovariantWith(builtinTypes->errorType, superTy));
|
||||
}
|
||||
else if (get<UnknownType>(superTy))
|
||||
{
|
||||
LUAU_ASSERT(!get<AnyType>(subTy)); // TODO: replace with ice.
|
||||
LUAU_ASSERT(!get<UnionType>(subTy)); // TODO: replace with ice.
|
||||
LUAU_ASSERT(!get<AnyType>(subTy)); // TODO: replace with ice.
|
||||
LUAU_ASSERT(!get<UnionType>(subTy)); // TODO: replace with ice.
|
||||
LUAU_ASSERT(!get<IntersectionType>(subTy)); // TODO: replace with ice.
|
||||
|
||||
bool errorSuppressing = get<ErrorType>(subTy);
|
||||
@ -185,6 +195,12 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
||||
return {false, true};
|
||||
else if (get<ErrorType>(subTy))
|
||||
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)
|
||||
{
|
||||
bool ok = bindGeneric(subTy, superTy);
|
||||
@ -196,32 +212,32 @@ SubtypingResult Subtyping::isSubtype_(TypeId subTy, TypeId superTy)
|
||||
return {ok};
|
||||
}
|
||||
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<TableType, TableType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
|
||||
return isSubtype_(p);
|
||||
return isCovariantWith(p);
|
||||
|
||||
return {false};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
||||
SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
subTp = follow(subTp);
|
||||
superTp = follow(superTp);
|
||||
@ -241,7 +257,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
||||
|
||||
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)
|
||||
return {false};
|
||||
}
|
||||
@ -255,7 +271,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
||||
if (auto vt = get<VariadicTypePack>(*subTail))
|
||||
{
|
||||
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))
|
||||
{
|
||||
@ -266,11 +282,11 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
||||
// <X>(X) -> () <: (T) -> ()
|
||||
|
||||
// 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);
|
||||
|
||||
if (TypePackId* other = mappedGenericPacks.find(*subTail))
|
||||
results.push_back(isSubtype_(*other, superTailPack));
|
||||
results.push_back(isCovariantWith(*other, superTailPack));
|
||||
else
|
||||
mappedGenericPacks.try_insert(*subTail, superTailPack);
|
||||
|
||||
@ -300,7 +316,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
||||
if (auto vt = get<VariadicTypePack>(*superTail))
|
||||
{
|
||||
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))
|
||||
{
|
||||
@ -311,11 +327,11 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
||||
// <X...>(X...) -> () <: (T) -> ()
|
||||
|
||||
// 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);
|
||||
|
||||
if (TypePackId* other = mappedGenericPacks.find(*superTail))
|
||||
results.push_back(isSubtype_(*other, subTailPack));
|
||||
results.push_back(isCovariantWith(*other, subTailPack));
|
||||
else
|
||||
mappedGenericPacks.try_insert(*superTail, subTailPack);
|
||||
|
||||
@ -344,7 +360,7 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
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))
|
||||
{
|
||||
@ -380,7 +396,8 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
@ -428,9 +445,33 @@ SubtypingResult Subtyping::isSubtype_(TypePackId subTp, TypePackId superTp)
|
||||
}
|
||||
|
||||
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
|
||||
* 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
|
||||
std::vector<SubtypingResult> subtypings;
|
||||
for (TypeId ty : superUnion)
|
||||
subtypings.push_back(isSubtype_(subTy, ty));
|
||||
subtypings.push_back(isCovariantWith(subTy, ty));
|
||||
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
|
||||
std::vector<SubtypingResult> subtypings;
|
||||
for (TypeId ty : subUnion)
|
||||
subtypings.push_back(isSubtype_(ty, superTy));
|
||||
subtypings.push_back(isCovariantWith(ty, superTy));
|
||||
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
|
||||
std::vector<SubtypingResult> subtypings;
|
||||
for (TypeId ty : superIntersection)
|
||||
subtypings.push_back(isSubtype_(subTy, ty));
|
||||
subtypings.push_back(isCovariantWith(subTy, ty));
|
||||
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
|
||||
std::vector<SubtypingResult> subtypings;
|
||||
for (TypeId ty : subIntersection)
|
||||
subtypings.push_back(isSubtype_(ty, superTy));
|
||||
subtypings.push_back(isCovariantWith(ty, superTy));
|
||||
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};
|
||||
}
|
||||
|
||||
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)
|
||||
return {true};
|
||||
@ -515,24 +727,20 @@ SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const P
|
||||
return {false};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const SingletonType* superSingleton)
|
||||
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const SingletonType* 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};
|
||||
|
||||
for (const auto& [name, prop]: superTable->props)
|
||||
for (const auto& [name, prop] : superTable->props)
|
||||
{
|
||||
auto it = subTable->props.find(name);
|
||||
if (it != subTable->props.end())
|
||||
{
|
||||
// Table properties are invariant
|
||||
result.andAlso(isSubtype(it->second.type(), prop.type()));
|
||||
result.andAlso(isSubtype(prop.type(), it->second.type()));
|
||||
}
|
||||
result.andAlso(isInvariantWith(prop.type(), it->second.type()));
|
||||
else
|
||||
return SubtypingResult{false};
|
||||
}
|
||||
@ -540,17 +748,18 @@ SubtypingResult Subtyping::isSubtype_(const TableType* subTable, const TableType
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype_(const MetatableType* subMt, const MetatableType* superMt)
|
||||
SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const MetatableType* superMt)
|
||||
{
|
||||
return SubtypingResult::all({
|
||||
isSubtype_(subMt->table, superMt->table),
|
||||
isSubtype_(subMt->metatable, superMt->metatable),
|
||||
isCovariantWith(subMt->table, superMt->table),
|
||||
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
|
||||
// the subtyping rule for this is just if the table component is a subtype
|
||||
// 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
|
||||
// compatible properties/shapes. We'll revisit this later when we have a
|
||||
// better understanding of how important this is.
|
||||
return isSubtype_(subTable, superTable);
|
||||
return isCovariantWith(subTable, superTable);
|
||||
}
|
||||
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)};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType* superTable)
|
||||
SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const TableType* superTable)
|
||||
{
|
||||
SubtypingResult result{true};
|
||||
|
||||
for (const auto& [name, prop]: superTable->props)
|
||||
for (const auto& [name, prop] : superTable->props)
|
||||
{
|
||||
if (auto classProp = lookupClassProp(subClass, name))
|
||||
{
|
||||
// Table properties are invariant
|
||||
result.andAlso(isSubtype_(classProp->type(), prop.type()));
|
||||
result.andAlso(isSubtype_(prop.type(), classProp->type()));
|
||||
}
|
||||
result.andAlso(isInvariantWith(prop.type(), classProp->type()));
|
||||
else
|
||||
return SubtypingResult{false};
|
||||
}
|
||||
@ -593,20 +798,20 @@ SubtypingResult Subtyping::isSubtype_(const ClassType* subClass, const TableType
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype_(const FunctionType* subFunction, const FunctionType* superFunction)
|
||||
SubtypingResult Subtyping::isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction)
|
||||
{
|
||||
SubtypingResult result;
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype_(const PrimitiveType* subPrim, const TableType* superTable)
|
||||
SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable)
|
||||
{
|
||||
SubtypingResult result{false};
|
||||
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 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;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype_(const SingletonType* subSingleton, const TableType* superTable)
|
||||
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const TableType* superTable)
|
||||
{
|
||||
SubtypingResult result{false};
|
||||
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 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;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype_(const NormalizedType* subNorm, const NormalizedType* superNorm)
|
||||
SubtypingResult Subtyping::isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm)
|
||||
{
|
||||
if (!subNorm || !superNorm)
|
||||
return {false, true, true};
|
||||
|
||||
SubtypingResult result = isSubtype_(subNorm->tops, superNorm->tops);
|
||||
result.andAlso(isSubtype_(subNorm->booleans, superNorm->booleans));
|
||||
result.andAlso(isSubtype_(subNorm->classes, superNorm->classes, superNorm->tables));
|
||||
result.andAlso(isSubtype_(subNorm->errors, superNorm->errors));
|
||||
result.andAlso(isSubtype_(subNorm->nils, superNorm->nils));
|
||||
result.andAlso(isSubtype_(subNorm->numbers, superNorm->numbers));
|
||||
result.isSubtype &= Luau::isSubtype(subNorm->strings, superNorm->strings);
|
||||
// isSubtype_(subNorm->strings, superNorm->tables);
|
||||
result.andAlso(isSubtype_(subNorm->threads, superNorm->threads));
|
||||
result.andAlso(isSubtype_(subNorm->tables, superNorm->tables));
|
||||
// isSubtype_(subNorm->tables, superNorm->strings);
|
||||
// isSubtype_(subNorm->tables, superNorm->classes);
|
||||
result.andAlso(isSubtype_(subNorm->functions, superNorm->functions));
|
||||
// isSubtype_(subNorm->tyvars, superNorm->tyvars);
|
||||
SubtypingResult result = isCovariantWith(subNorm->tops, superNorm->tops);
|
||||
result.andAlso(isCovariantWith(subNorm->booleans, superNorm->booleans));
|
||||
result.andAlso(isCovariantWith(subNorm->classes, superNorm->classes).orElse(isCovariantWith(subNorm->classes, superNorm->tables)));
|
||||
result.andAlso(isCovariantWith(subNorm->errors, superNorm->errors));
|
||||
result.andAlso(isCovariantWith(subNorm->nils, superNorm->nils));
|
||||
result.andAlso(isCovariantWith(subNorm->numbers, superNorm->numbers));
|
||||
result.andAlso(isCovariantWith(subNorm->strings, superNorm->strings));
|
||||
result.andAlso(isCovariantWith(subNorm->strings, superNorm->tables));
|
||||
result.andAlso(isCovariantWith(subNorm->threads, superNorm->threads));
|
||||
result.andAlso(isCovariantWith(subNorm->tables, superNorm->tables));
|
||||
result.andAlso(isCovariantWith(subNorm->functions, superNorm->functions));
|
||||
// isCovariantWith(subNorm->tyvars, superNorm->tyvars);
|
||||
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)
|
||||
{
|
||||
@ -677,24 +880,18 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const
|
||||
|
||||
for (const auto& [superClassTy, superNegations] : superClass.classes)
|
||||
{
|
||||
result.orElse(isSubtype_(subClassTy, superClassTy));
|
||||
result.orElse(isCovariantWith(subClassTy, superClassTy));
|
||||
if (!result.isSubtype)
|
||||
continue;
|
||||
|
||||
for (TypeId negation : superNegations)
|
||||
{
|
||||
result.andAlso(SubtypingResult::negate(isSubtype_(subClassTy, negation)));
|
||||
result.andAlso(SubtypingResult::negate(isCovariantWith(subClassTy, negation)));
|
||||
if (result.isSubtype)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isSubtype)
|
||||
continue;
|
||||
|
||||
for (TypeId superTableTy : superTables)
|
||||
result.orElse(isSubtype_(subClassTy, superTableTy));
|
||||
|
||||
if (!result.isSubtype)
|
||||
return result;
|
||||
}
|
||||
@ -702,17 +899,79 @@ SubtypingResult Subtyping::isSubtype_(const NormalizedClassType& subClass, const
|
||||
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())
|
||||
return {true};
|
||||
else if (superFunction.isTop)
|
||||
return {true};
|
||||
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;
|
||||
|
||||
@ -720,15 +979,15 @@ SubtypingResult Subtyping::isSubtype_(const TypeIds& subTypes, const TypeIds& su
|
||||
{
|
||||
results.emplace_back();
|
||||
for (TypeId superTy : superTypes)
|
||||
results.back().orElse(isSubtype_(subTy, superTy));
|
||||
results.back().orElse(isCovariantWith(subTy, superTy));
|
||||
}
|
||||
|
||||
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)
|
||||
@ -772,7 +1031,7 @@ bool Subtyping::bindGeneric(TypePackId subTp, TypePackId superTp)
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename Container>
|
||||
template<typename T, typename Container>
|
||||
TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
|
||||
{
|
||||
if (container.empty())
|
||||
|
@ -145,8 +145,7 @@ void StateDot::visitChildren(TypeId ty, int index)
|
||||
startNode(index);
|
||||
startNodeLabel();
|
||||
|
||||
auto go = [&](auto&& t)
|
||||
{
|
||||
auto go = [&](auto&& t) {
|
||||
using T = std::decay_t<decltype(t)>;
|
||||
|
||||
if constexpr (std::is_same_v<T, BoundType>)
|
||||
|
@ -242,8 +242,8 @@ struct TypeChecker2
|
||||
|
||||
Normalizer normalizer;
|
||||
|
||||
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule* sourceModule,
|
||||
Module* module)
|
||||
TypeChecker2(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
|
||||
const SourceModule* sourceModule, Module* module)
|
||||
: builtinTypes(builtinTypes)
|
||||
, logger(logger)
|
||||
, limits(limits)
|
||||
@ -1295,13 +1295,8 @@ struct TypeChecker2
|
||||
else if (auto assertion = expr->as<AstExprTypeAssertion>())
|
||||
return isLiteral(assertion->expr);
|
||||
|
||||
return
|
||||
expr->is<AstExprConstantNil>() ||
|
||||
expr->is<AstExprConstantBool>() ||
|
||||
expr->is<AstExprConstantNumber>() ||
|
||||
expr->is<AstExprConstantString>() ||
|
||||
expr->is<AstExprFunction>() ||
|
||||
expr->is<AstExprTable>();
|
||||
return expr->is<AstExprConstantNil>() || expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNumber>() ||
|
||||
expr->is<AstExprConstantString>() || expr->is<AstExprFunction>() || expr->is<AstExprTable>();
|
||||
}
|
||||
|
||||
static std::unique_ptr<LiteralProperties> buildLiteralPropertiesSet(AstExpr* expr)
|
||||
@ -1423,7 +1418,7 @@ struct TypeChecker2
|
||||
LUAU_ASSERT(argOffset == args->head.size());
|
||||
|
||||
const Location argLoc = argExprs->empty() ? Location{} // TODO
|
||||
: argExprs->at(argExprs->size() - 1)->location;
|
||||
: argExprs->at(argExprs->size() - 1)->location;
|
||||
|
||||
if (paramIter.tail() && args->tail)
|
||||
{
|
||||
@ -2686,8 +2681,8 @@ struct TypeChecker2
|
||||
}
|
||||
};
|
||||
|
||||
void check(
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, const SourceModule& sourceModule, Module* module)
|
||||
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
|
||||
const SourceModule& sourceModule, Module* module)
|
||||
{
|
||||
TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module};
|
||||
|
||||
|
@ -25,6 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
|
||||
LUAU_FASTFLAG(LuauNormalizeBlockedTypes)
|
||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -2285,7 +2286,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
|
||||
variance = Invariant;
|
||||
|
||||
Unifier innerState = makeChildUnifier();
|
||||
if (useNewSolver)
|
||||
if (useNewSolver || FFlag::LuauFixIndexerSubtypingOrdering)
|
||||
innerState.tryUnify_(prop.type(), superTable->indexer->indexResultType);
|
||||
else
|
||||
{
|
||||
|
@ -26,7 +26,6 @@ Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes,
|
||||
, ice(ice)
|
||||
, recursionLimit(FInt::LuauTypeInferRecursionLimit)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||
@ -99,10 +98,7 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t maxLength = std::max(
|
||||
flatten(subTp).first.size(),
|
||||
flatten(superTp).first.size()
|
||||
);
|
||||
size_t maxLength = std::max(flatten(subTp).first.size(), flatten(superTp).first.size());
|
||||
|
||||
auto [subTypes, subTail] = extendTypePack(*arena, builtinTypes, subTp, maxLength);
|
||||
auto [superTypes, superTail] = extendTypePack(*arena, builtinTypes, superTp, maxLength);
|
||||
@ -123,16 +119,25 @@ struct FreeTypeSearcher : TypeVisitor
|
||||
explicit FreeTypeSearcher(NotNull<Scope> scope)
|
||||
: TypeVisitor(/*skipBoundTypes*/ true)
|
||||
, scope(scope)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
enum { Positive, Negative } polarity = Positive;
|
||||
enum
|
||||
{
|
||||
Positive,
|
||||
Negative
|
||||
} polarity = Positive;
|
||||
|
||||
void flip()
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive: polarity = Negative; break;
|
||||
case Negative: polarity = Positive; break;
|
||||
case Positive:
|
||||
polarity = Negative;
|
||||
break;
|
||||
case Negative:
|
||||
polarity = Positive;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,8 +157,12 @@ struct FreeTypeSearcher : TypeVisitor
|
||||
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive: positiveTypes.insert(ty); break;
|
||||
case Negative: negativeTypes.insert(ty); break;
|
||||
case Positive:
|
||||
positiveTypes.insert(ty);
|
||||
break;
|
||||
case Negative:
|
||||
negativeTypes.insert(ty);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -180,13 +189,17 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
||||
std::unordered_set<TypeId> negativeTypes;
|
||||
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)
|
||||
, builtinTypes(builtinTypes)
|
||||
, scope(scope)
|
||||
, positiveTypes(std::move(positiveTypes))
|
||||
, negativeTypes(std::move(negativeTypes))
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
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
|
||||
option = follow(option);
|
||||
if (option == needle)
|
||||
{
|
||||
LUAU_ASSERT(!seen.find(option));
|
||||
option = replacement;
|
||||
}
|
||||
|
||||
// TODO seen set
|
||||
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);
|
||||
LUAU_ASSERT(ft);
|
||||
@ -232,7 +256,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
||||
traverse(ft->lowerBound);
|
||||
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);
|
||||
ft = get<FreeType>(ty);
|
||||
if (!ft)
|
||||
@ -252,8 +277,13 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
||||
|
||||
if (!hasLowerBound && !hasUpperBound)
|
||||
{
|
||||
emplaceType<GenericType>(asMutable(ty), scope);
|
||||
generics.push_back(ty);
|
||||
if (isWithinFunction)
|
||||
{
|
||||
emplaceType<GenericType>(asMutable(ty), scope);
|
||||
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
|
||||
@ -264,19 +294,27 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
||||
// If we do not do this, we get tautological bounds like a <: a <: unknown.
|
||||
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;
|
||||
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
|
||||
{
|
||||
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;
|
||||
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;
|
||||
@ -363,4 +401,4 @@ OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypePackId>& seen, TypePack
|
||||
return OccursCheckResult::Pass;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
@ -534,7 +534,7 @@ class AstStatBlock : public AstStat
|
||||
public:
|
||||
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;
|
||||
|
||||
|
@ -89,13 +89,14 @@ static void reportError(const char* name, const Luau::CompileError& error)
|
||||
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);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
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);
|
||||
return "";
|
||||
@ -119,6 +120,8 @@ struct CompileStats
|
||||
double parseTime;
|
||||
double compileTime;
|
||||
double codegenTime;
|
||||
|
||||
Luau::CodeGen::LoweringStats lowerStats;
|
||||
};
|
||||
|
||||
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::CodegenIr:
|
||||
case CompileFormat::CodegenVerbose:
|
||||
printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options).c_str());
|
||||
printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options, &stats.lowerStats).c_str());
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
case CompileFormat::Null:
|
||||
@ -355,13 +358,22 @@ int main(int argc, char** argv)
|
||||
failed += !compileFile(path.c_str(), compileFormat, assemblyTarget, stats);
|
||||
|
||||
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),
|
||||
stats.readTime, stats.parseTime, stats.compileTime);
|
||||
}
|
||||
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",
|
||||
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.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;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct lua_State;
|
||||
@ -74,8 +75,18 @@ struct AssemblyOptions
|
||||
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
|
||||
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);
|
||||
|
||||
|
@ -12,6 +12,9 @@ namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
struct LoweringStats;
|
||||
|
||||
namespace X64
|
||||
{
|
||||
|
||||
@ -33,7 +36,7 @@ struct IrSpillX64
|
||||
|
||||
struct IrRegAllocX64
|
||||
{
|
||||
IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function);
|
||||
IrRegAllocX64(AssemblyBuilderX64& build, IrFunction& function, LoweringStats* stats);
|
||||
|
||||
RegisterX64 allocReg(SizeX64 size, uint32_t instIdx);
|
||||
RegisterX64 allocRegOrReuse(SizeX64 size, uint32_t instIdx, std::initializer_list<IrOp> oprefs);
|
||||
@ -70,6 +73,7 @@ struct IrRegAllocX64
|
||||
|
||||
AssemblyBuilderX64& build;
|
||||
IrFunction& function;
|
||||
LoweringStats* stats = nullptr;
|
||||
|
||||
uint32_t currInstIdx = ~0u;
|
||||
|
||||
|
@ -264,5 +264,14 @@ uint32_t getNativeContextOffset(int bfid);
|
||||
// Cleans up blocks that were created with no users
|
||||
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 Luau
|
||||
|
@ -1091,7 +1091,7 @@ void AssemblyBuilderA64::placeER(const char* name, RegisterA64 dst, RegisterA64
|
||||
LUAU_ASSERT(shift >= 0 && shift <= 4);
|
||||
|
||||
uint32_t sf = (dst.kind == KindA64::x) ? 0x80000000 : 0; // could be useful in the future for byte->word extends
|
||||
int option = 0b010; // UXTW
|
||||
int option = 0b010; // UXTW
|
||||
|
||||
place(dst.index | (src1.index << 5) | (shift << 10) | (option << 13) | (src2.index << 16) | (1 << 21) | (op << 24) | sf);
|
||||
commit();
|
||||
|
@ -106,7 +106,7 @@ static std::optional<NativeProto> createNativeFunction(AssemblyBuilder& build, M
|
||||
IrBuilder ir;
|
||||
ir.buildFunctionIr(proto);
|
||||
|
||||
if (!lowerFunction(ir, build, helpers, proto, {}))
|
||||
if (!lowerFunction(ir, build, helpers, proto, {}, /* stats */ nullptr))
|
||||
return std::nullopt;
|
||||
|
||||
return createNativeProto(proto, ir);
|
||||
|
@ -43,7 +43,7 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
|
||||
}
|
||||
|
||||
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;
|
||||
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)
|
||||
logFunctionHeader(build, p);
|
||||
|
||||
if (!lowerFunction(ir, build, helpers, p, options))
|
||||
if (!lowerFunction(ir, build, helpers, p, options, stats))
|
||||
{
|
||||
if (build.logText)
|
||||
build.logAppend("; skipping (can't lower)\n");
|
||||
@ -90,7 +90,7 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
||||
unsigned int getCpuFeaturesA64();
|
||||
#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));
|
||||
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);
|
||||
#endif
|
||||
|
||||
return getAssemblyImpl(build, func, options);
|
||||
return getAssemblyImpl(build, func, options, stats);
|
||||
}
|
||||
|
||||
case AssemblyOptions::A64:
|
||||
{
|
||||
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:
|
||||
{
|
||||
A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, /* features= */ 0);
|
||||
|
||||
return getAssemblyImpl(build, func, options);
|
||||
return getAssemblyImpl(build, func, options, stats);
|
||||
}
|
||||
|
||||
case AssemblyOptions::X64_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:
|
||||
{
|
||||
X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, X64::ABIX64::SystemV);
|
||||
|
||||
return getAssemblyImpl(build, func, options);
|
||||
return getAssemblyImpl(build, func, options, stats);
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -44,42 +44,10 @@ inline void gatherFunctions(std::vector<Proto*>& results, Proto* proto)
|
||||
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>
|
||||
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;
|
||||
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;
|
||||
});
|
||||
std::vector<uint32_t> sortedBlocks = getSortedBlockOrder(function);
|
||||
|
||||
// For each IR instruction that begins a bytecode instruction, which bytecode instruction is it?
|
||||
std::vector<uint32_t> bcLocations(function.instructions.size() + 1, ~0u);
|
||||
@ -231,24 +199,26 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
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);
|
||||
|
||||
X64::IrLoweringX64 lowering(build, helpers, ir.function);
|
||||
X64::IrLoweringX64 lowering(build, helpers, ir.function, stats);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -264,7 +234,7 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
|
||||
createLinearBlocks(ir, useValueNumbering);
|
||||
}
|
||||
|
||||
return lowerIr(build, ir, helpers, proto, options);
|
||||
return lowerIr(build, ir, helpers, proto, options, stats);
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
|
@ -385,7 +385,8 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i)
|
||||
translateInstDupTable(*this, pc, i);
|
||||
break;
|
||||
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;
|
||||
case LOP_GETUPVAL:
|
||||
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)
|
||||
, helpers(helpers)
|
||||
, 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)
|
||||
, exitHandlerMap(~0u)
|
||||
{
|
||||
@ -858,7 +859,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
|
||||
build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaH_setnum)));
|
||||
build.blr(x3);
|
||||
inst.regA64 = regs.takeReg(x0, index);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
case IrCmd::NEW_TABLE:
|
||||
{
|
||||
@ -2016,6 +2017,15 @@ void IrLoweringA64::finishFunction()
|
||||
build.mov(x0, handler.pcpos * sizeof(Instruction));
|
||||
build.b(helpers.updatePcAndContinueInVm);
|
||||
}
|
||||
|
||||
if (stats)
|
||||
{
|
||||
if (error)
|
||||
stats->loweringErrors++;
|
||||
|
||||
if (regs.error)
|
||||
stats->regAllocErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
bool IrLoweringA64::hasError() const
|
||||
|
@ -17,13 +17,14 @@ namespace CodeGen
|
||||
|
||||
struct ModuleHelpers;
|
||||
struct AssemblyOptions;
|
||||
struct LoweringStats;
|
||||
|
||||
namespace A64
|
||||
{
|
||||
|
||||
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 finishBlock(const IrBlock& curr, const IrBlock& next);
|
||||
@ -74,6 +75,7 @@ struct IrLoweringA64
|
||||
ModuleHelpers& helpers;
|
||||
|
||||
IrFunction& function;
|
||||
LoweringStats* stats = nullptr;
|
||||
|
||||
IrRegAllocA64 regs;
|
||||
|
||||
|
@ -22,11 +22,12 @@ namespace CodeGen
|
||||
namespace X64
|
||||
{
|
||||
|
||||
IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function)
|
||||
IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, IrFunction& function, LoweringStats* stats)
|
||||
: build(build)
|
||||
, helpers(helpers)
|
||||
, function(function)
|
||||
, regs(build, function)
|
||||
, stats(stats)
|
||||
, regs(build, function, stats)
|
||||
, valueTracker(function)
|
||||
, exitHandlerMap(~0u)
|
||||
{
|
||||
@ -1646,6 +1647,15 @@ void IrLoweringX64::finishFunction()
|
||||
build.mov(edx, handler.pcpos * sizeof(Instruction));
|
||||
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
|
||||
|
@ -19,13 +19,14 @@ namespace CodeGen
|
||||
|
||||
struct ModuleHelpers;
|
||||
struct AssemblyOptions;
|
||||
struct LoweringStats;
|
||||
|
||||
namespace X64
|
||||
{
|
||||
|
||||
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 finishBlock(const IrBlock& curr, const IrBlock& next);
|
||||
@ -76,6 +77,7 @@ struct IrLoweringX64
|
||||
ModuleHelpers& helpers;
|
||||
|
||||
IrFunction& function;
|
||||
LoweringStats* stats = nullptr;
|
||||
|
||||
IrRegAllocX64 regs;
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "IrRegAllocA64.h"
|
||||
|
||||
#include "Luau/AssemblyBuilderA64.h"
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "BitUtils.h"
|
||||
@ -109,8 +110,9 @@ static void restoreInst(AssemblyBuilderA64& build, uint32_t& freeSpillSlots, IrF
|
||||
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)
|
||||
, stats(stats)
|
||||
{
|
||||
for (auto& p : regs)
|
||||
{
|
||||
@ -329,6 +331,9 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init
|
||||
spills.push_back(s);
|
||||
|
||||
def.needsReload = true;
|
||||
|
||||
if (stats)
|
||||
stats->spillsToRestore++;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -345,6 +350,14 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init
|
||||
spills.push_back(s);
|
||||
|
||||
def.spilled = true;
|
||||
|
||||
if (stats)
|
||||
{
|
||||
stats->spillsToSlot++;
|
||||
|
||||
if (slot != kInvalidSpill && unsigned(slot + 1) > stats->maxSpillSlotsUsed)
|
||||
stats->maxSpillSlotsUsed = slot + 1;
|
||||
}
|
||||
}
|
||||
|
||||
def.regA64 = noreg;
|
||||
|
@ -12,6 +12,9 @@ namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
struct LoweringStats;
|
||||
|
||||
namespace A64
|
||||
{
|
||||
|
||||
@ -19,7 +22,7 @@ class AssemblyBuilderA64;
|
||||
|
||||
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 allocTemp(KindA64 kind);
|
||||
@ -69,6 +72,7 @@ struct IrRegAllocA64
|
||||
Set& getSet(KindA64 kind);
|
||||
|
||||
IrFunction& function;
|
||||
LoweringStats* stats = nullptr;
|
||||
Set gpr, simd;
|
||||
|
||||
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
|
||||
#include "Luau/IrRegAllocX64.h"
|
||||
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
#include "EmitCommonX64.h"
|
||||
@ -14,9 +15,10 @@ namespace X64
|
||||
|
||||
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)
|
||||
, function(function)
|
||||
, stats(stats)
|
||||
, usableXmmRegCount(getXmmRegisterCount(build.abi))
|
||||
{
|
||||
freeGprMap.fill(true);
|
||||
@ -225,10 +227,16 @@ void IrRegAllocX64::preserve(IrInst& inst)
|
||||
|
||||
spill.stackSlot = uint8_t(i);
|
||||
inst.spilled = true;
|
||||
|
||||
if (stats)
|
||||
stats->spillsToSlot++;
|
||||
}
|
||||
else
|
||||
{
|
||||
inst.needsReload = true;
|
||||
|
||||
if (stats)
|
||||
stats->spillsToRestore++;
|
||||
}
|
||||
|
||||
spills.push_back(spill);
|
||||
|
@ -615,7 +615,7 @@ static IrOp getLoopStepK(IrBuilder& build, int ra)
|
||||
{
|
||||
IrBlock& active = build.function.blocks[build.activeBlockIdx];
|
||||
|
||||
if (active.start + 2 < build.function.instructions.size())
|
||||
if (active.start + 2 < build.function.instructions.size())
|
||||
{
|
||||
IrInst& sv = build.function.instructions[build.function.instructions.size() - 2];
|
||||
IrInst& st = build.function.instructions[build.function.instructions.size() - 1];
|
||||
@ -665,7 +665,7 @@ void translateInstForNPrep(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
IrOp step = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra + 1));
|
||||
|
||||
// step > 0
|
||||
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
|
||||
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse);
|
||||
|
||||
// Condition to start the loop: step > 0 ? idx <= limit : limit <= idx
|
||||
@ -763,7 +763,7 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
IrOp reverse = build.block(IrBlockKind::Internal);
|
||||
|
||||
// step > 0
|
||||
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
|
||||
// note: equivalent to 0 < step, but lowers into one instruction on both X64 and A64
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, step, zero, build.cond(IrCondition::Greater), direct, reverse);
|
||||
|
||||
// Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx
|
||||
@ -776,8 +776,8 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
build.beginBlock(direct);
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
|
||||
}
|
||||
else
|
||||
{
|
||||
else
|
||||
{
|
||||
double stepN = build.function.doubleOp(stepK);
|
||||
|
||||
// Condition to continue the loop: step > 0 ? idx <= limit : limit <= idx
|
||||
@ -785,9 +785,9 @@ void translateInstForNLoop(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, idx, limit, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
|
||||
else
|
||||
build.inst(IrCmd::JUMP_CMP_NUM, limit, idx, build.cond(IrCondition::LessEqual), loopRepeat, loopExit);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
build.inst(IrCmd::INTERRUPT, build.constUint(pcpos));
|
||||
|
||||
|
@ -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 Luau
|
||||
|
@ -14,21 +14,21 @@
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauCodeGenBlockSize, 4 * 1024 * 1024)
|
||||
LUAU_FASTINTVARIABLE(LuauCodeGenMaxTotalSize, 256 * 1024 * 1024)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
{
|
||||
|
||||
constexpr unsigned kBlockSize = 4 * 1024 * 1024;
|
||||
constexpr unsigned kMaxTotalSize = 256 * 1024 * 1024;
|
||||
|
||||
NativeState::NativeState()
|
||||
: NativeState(nullptr, nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
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[] = {
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"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
|
||||
nullptr,
|
||||
};
|
||||
|
@ -275,15 +275,15 @@ static int math_randomseed(lua_State* L)
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 151};
|
||||
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, 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, 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, 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, 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, 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, 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, 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, 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},
|
||||
{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
|
||||
// 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)
|
||||
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*>,
|
||||
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}}};
|
||||
return results;
|
||||
};
|
||||
|
@ -57,8 +57,7 @@ TEST_CASE("CodeAllocationCallbacks")
|
||||
|
||||
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);
|
||||
if (oldPointer != nullptr)
|
||||
{
|
||||
|
@ -74,7 +74,7 @@ TEST_CASE("BytecodeIsStable")
|
||||
|
||||
// Bytecode ops (serialized & in-memory)
|
||||
CHECK(LOP_FASTCALL2K == 75); // bytecode v1
|
||||
CHECK(LOP_JUMPXEQKS == 80); // bytecode v3
|
||||
CHECK(LOP_JUMPXEQKS == 80); // bytecode v3
|
||||
|
||||
// Bytecode fastcall ids (serialized & in-memory)
|
||||
// Note: these aren't strictly bound to specific bytecode versions, but must monotonically increase to keep backwards compat
|
||||
@ -7371,7 +7371,8 @@ TEST_CASE("BuiltinFoldMathK")
|
||||
function test()
|
||||
return math.pi * 2
|
||||
end
|
||||
)", 0, 2),
|
||||
)",
|
||||
0, 2),
|
||||
R"(
|
||||
LOADK R0 K0 [6.2831853071795862]
|
||||
RETURN R0 1
|
||||
@ -7382,7 +7383,8 @@ RETURN R0 1
|
||||
function test()
|
||||
return math.pi * 2
|
||||
end
|
||||
)", 0, 1),
|
||||
)",
|
||||
0, 1),
|
||||
R"(
|
||||
GETIMPORT R1 3 [math.pi]
|
||||
MULK R0 R1 K0 [2]
|
||||
@ -7396,7 +7398,8 @@ function test()
|
||||
end
|
||||
|
||||
math = { pi = 4 }
|
||||
)", 0, 2),
|
||||
)",
|
||||
0, 2),
|
||||
R"(
|
||||
GETGLOBAL R2 K1 ['math']
|
||||
GETTABLEKS R1 R2 K2 ['pi']
|
||||
|
@ -12,7 +12,7 @@ class IrCallWrapperX64Fixture
|
||||
public:
|
||||
IrCallWrapperX64Fixture(ABIX64 abi = ABIX64::Windows)
|
||||
: build(/* logText */ true, abi)
|
||||
, regs(build, function)
|
||||
, regs(build, function, nullptr)
|
||||
, callWrap(regs, build, ~0u)
|
||||
{
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class IrRegAllocX64Fixture
|
||||
public:
|
||||
IrRegAllocX64Fixture()
|
||||
: build(/* logText */ true, ABIX64::Windows)
|
||||
, regs(build, function)
|
||||
, regs(build, function, nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -516,6 +516,71 @@ struct NormalizeFixture : Fixture
|
||||
|
||||
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")
|
||||
{
|
||||
CHECK("number" == toString(normal(R"(
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
using namespace Luau;
|
||||
@ -17,7 +18,15 @@ struct SubtypeFixture : Fixture
|
||||
UnifierSharedState sharedState{&ice};
|
||||
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)
|
||||
{
|
||||
@ -66,11 +75,18 @@ struct SubtypeFixture : Fixture
|
||||
return arena.addType(UnionType{{a, b}});
|
||||
}
|
||||
|
||||
// `~`
|
||||
TypeId negate(TypeId 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)
|
||||
{
|
||||
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))});
|
||||
}
|
||||
|
||||
TypeId genericT = arena.addType(GenericType{"T"});
|
||||
TypeId genericU = arena.addType(GenericType{"U"});
|
||||
TypeId genericT = arena.addType(GenericType{moduleScope.get(), "T"});
|
||||
TypeId genericU = arena.addType(GenericType{moduleScope.get(), "U"});
|
||||
|
||||
TypePackId genericAs = arena.addTypePack(GenericTypePack{"A"});
|
||||
TypePackId genericBs = arena.addTypePack(GenericTypePack{"B"});
|
||||
@ -113,6 +129,10 @@ struct SubtypeFixture : Fixture
|
||||
TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}});
|
||||
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 trueOrFalseType = join(builtinTypes->trueType, builtinTypes->falseType);
|
||||
|
||||
@ -128,7 +148,7 @@ struct SubtypeFixture : Fixture
|
||||
* \- AnotherChild
|
||||
* |- AnotherGrandchildOne
|
||||
* \- AnotherGrandchildTwo
|
||||
*/
|
||||
*/
|
||||
TypeId rootClass = cls("Root");
|
||||
TypeId childClass = cls("Child", rootClass);
|
||||
TypeId grandchildOneClass = cls("GrandchildOne", childClass);
|
||||
@ -138,9 +158,9 @@ struct SubtypeFixture : Fixture
|
||||
TypeId anotherGrandchildTwoClass = cls("AnotherGrandchildTwo", anotherChildClass);
|
||||
|
||||
TypeId vec2Class = cls("Vec2", {
|
||||
{"X", builtinTypes->numberType},
|
||||
{"Y", builtinTypes->numberType},
|
||||
});
|
||||
{"X", builtinTypes->numberType},
|
||||
{"Y", builtinTypes->numberType},
|
||||
});
|
||||
|
||||
// "hello" | "hello"
|
||||
TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}});
|
||||
@ -149,160 +169,76 @@ struct SubtypeFixture : Fixture
|
||||
const TypeId nothingToNothingType = fn({}, {});
|
||||
|
||||
// (number) -> string
|
||||
const TypeId numberToStringType = fn(
|
||||
{builtinTypes->numberType},
|
||||
{builtinTypes->stringType}
|
||||
);
|
||||
const TypeId numberToStringType = fn({builtinTypes->numberType}, {builtinTypes->stringType});
|
||||
|
||||
// (unknown) -> string
|
||||
const TypeId unknownToStringType = fn(
|
||||
{builtinTypes->unknownType},
|
||||
{builtinTypes->stringType}
|
||||
);
|
||||
const TypeId unknownToStringType = fn({builtinTypes->unknownType}, {builtinTypes->stringType});
|
||||
|
||||
// (number) -> ()
|
||||
const TypeId numberToNothingType = fn(
|
||||
{builtinTypes->numberType},
|
||||
{}
|
||||
);
|
||||
const TypeId numberToNothingType = fn({builtinTypes->numberType}, {});
|
||||
|
||||
// () -> number
|
||||
const TypeId nothingToNumberType = fn(
|
||||
{},
|
||||
{builtinTypes->numberType}
|
||||
);
|
||||
const TypeId nothingToNumberType = fn({}, {builtinTypes->numberType});
|
||||
|
||||
// (number) -> number
|
||||
const TypeId numberToNumberType = fn(
|
||||
{builtinTypes->numberType},
|
||||
{builtinTypes->numberType}
|
||||
);
|
||||
const TypeId numberToNumberType = fn({builtinTypes->numberType}, {builtinTypes->numberType});
|
||||
|
||||
// (number) -> unknown
|
||||
const TypeId numberToUnknownType = fn(
|
||||
{builtinTypes->numberType},
|
||||
{builtinTypes->unknownType}
|
||||
);
|
||||
const TypeId numberToUnknownType = fn({builtinTypes->numberType}, {builtinTypes->unknownType});
|
||||
|
||||
// (number) -> (string, string)
|
||||
const TypeId numberToTwoStringsType = fn(
|
||||
{builtinTypes->numberType},
|
||||
{builtinTypes->stringType, builtinTypes->stringType}
|
||||
);
|
||||
const TypeId numberToTwoStringsType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->stringType});
|
||||
|
||||
// (number) -> (string, unknown)
|
||||
const TypeId numberToStringAndUnknownType = fn(
|
||||
{builtinTypes->numberType},
|
||||
{builtinTypes->stringType, builtinTypes->unknownType}
|
||||
);
|
||||
const TypeId numberToStringAndUnknownType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->unknownType});
|
||||
|
||||
// (number, number) -> string
|
||||
const TypeId numberNumberToStringType = fn(
|
||||
{builtinTypes->numberType, builtinTypes->numberType},
|
||||
{builtinTypes->stringType}
|
||||
);
|
||||
const TypeId numberNumberToStringType = fn({builtinTypes->numberType, builtinTypes->numberType}, {builtinTypes->stringType});
|
||||
|
||||
// (unknown, number) -> string
|
||||
const TypeId unknownNumberToStringType = fn(
|
||||
{builtinTypes->unknownType, builtinTypes->numberType},
|
||||
{builtinTypes->stringType}
|
||||
);
|
||||
const TypeId unknownNumberToStringType = fn({builtinTypes->unknownType, builtinTypes->numberType}, {builtinTypes->stringType});
|
||||
|
||||
// (number, string) -> string
|
||||
const TypeId numberAndStringToStringType = fn(
|
||||
{builtinTypes->numberType, builtinTypes->stringType},
|
||||
{builtinTypes->stringType}
|
||||
);
|
||||
const TypeId numberAndStringToStringType = fn({builtinTypes->numberType, builtinTypes->stringType}, {builtinTypes->stringType});
|
||||
|
||||
// (number, ...string) -> string
|
||||
const TypeId numberAndStringsToStringType = fn(
|
||||
{builtinTypes->numberType}, VariadicTypePack{builtinTypes->stringType},
|
||||
{builtinTypes->stringType}
|
||||
);
|
||||
const TypeId numberAndStringsToStringType =
|
||||
fn({builtinTypes->numberType}, VariadicTypePack{builtinTypes->stringType}, {builtinTypes->stringType});
|
||||
|
||||
// (number, ...string?) -> string
|
||||
const TypeId numberAndOptionalStringsToStringType = fn(
|
||||
{builtinTypes->numberType}, VariadicTypePack{builtinTypes->optionalStringType},
|
||||
{builtinTypes->stringType}
|
||||
);
|
||||
const TypeId numberAndOptionalStringsToStringType =
|
||||
fn({builtinTypes->numberType}, VariadicTypePack{builtinTypes->optionalStringType}, {builtinTypes->stringType});
|
||||
|
||||
// (...number) -> number
|
||||
const TypeId numbersToNumberType = arena.addType(FunctionType{
|
||||
arena.addTypePack(VariadicTypePack{builtinTypes->numberType}),
|
||||
arena.addTypePack({builtinTypes->numberType})
|
||||
});
|
||||
const TypeId numbersToNumberType =
|
||||
arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->numberType}), arena.addTypePack({builtinTypes->numberType})});
|
||||
|
||||
// <T>(T) -> ()
|
||||
const TypeId genericTToNothingType = arena.addType(FunctionType{
|
||||
{genericT},
|
||||
{},
|
||||
arena.addTypePack({genericT}),
|
||||
builtinTypes->emptyTypePack
|
||||
});
|
||||
const TypeId genericTToNothingType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({genericT}), builtinTypes->emptyTypePack});
|
||||
|
||||
// <T>(T) -> T
|
||||
const TypeId genericTToTType = arena.addType(FunctionType{
|
||||
{genericT},
|
||||
{},
|
||||
arena.addTypePack({genericT}),
|
||||
arena.addTypePack({genericT})
|
||||
});
|
||||
const TypeId genericTToTType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({genericT}), arena.addTypePack({genericT})});
|
||||
|
||||
// <U>(U) -> ()
|
||||
const TypeId genericUToNothingType = arena.addType(FunctionType{
|
||||
{genericU},
|
||||
{},
|
||||
arena.addTypePack({genericU}),
|
||||
builtinTypes->emptyTypePack
|
||||
});
|
||||
const TypeId genericUToNothingType = arena.addType(FunctionType{{genericU}, {}, arena.addTypePack({genericU}), builtinTypes->emptyTypePack});
|
||||
|
||||
// <T>() -> T
|
||||
const TypeId genericNothingToTType = arena.addType(FunctionType{
|
||||
{genericT},
|
||||
{},
|
||||
builtinTypes->emptyTypePack,
|
||||
arena.addTypePack({genericT})
|
||||
});
|
||||
const TypeId genericNothingToTType = arena.addType(FunctionType{{genericT}, {}, builtinTypes->emptyTypePack, arena.addTypePack({genericT})});
|
||||
|
||||
// <A...>(A...) -> A...
|
||||
const TypeId genericAsToAsType = arena.addType(FunctionType{
|
||||
{},
|
||||
{genericAs},
|
||||
genericAs,
|
||||
genericAs
|
||||
});
|
||||
const TypeId genericAsToAsType = arena.addType(FunctionType{{}, {genericAs}, genericAs, genericAs});
|
||||
|
||||
// <A...>(A...) -> number
|
||||
const TypeId genericAsToNumberType = arena.addType(FunctionType{
|
||||
{},
|
||||
{genericAs},
|
||||
genericAs,
|
||||
arena.addTypePack({builtinTypes->numberType})
|
||||
});
|
||||
const TypeId genericAsToNumberType = arena.addType(FunctionType{{}, {genericAs}, genericAs, arena.addTypePack({builtinTypes->numberType})});
|
||||
|
||||
// <B...>(B...) -> B...
|
||||
const TypeId genericBsToBsType = arena.addType(FunctionType{
|
||||
{},
|
||||
{genericBs},
|
||||
genericBs,
|
||||
genericBs
|
||||
});
|
||||
const TypeId genericBsToBsType = arena.addType(FunctionType{{}, {genericBs}, genericBs, genericBs});
|
||||
|
||||
// <B..., C...>(B...) -> C...
|
||||
const TypeId genericBsToCsType = arena.addType(FunctionType{
|
||||
{},
|
||||
{genericBs, genericCs},
|
||||
genericBs,
|
||||
genericCs
|
||||
});
|
||||
const TypeId genericBsToCsType = arena.addType(FunctionType{{}, {genericBs, genericCs}, genericBs, genericCs});
|
||||
|
||||
// <A...>() -> A...
|
||||
const TypeId genericNothingToAsType = arena.addType(FunctionType{
|
||||
{},
|
||||
{genericAs},
|
||||
builtinTypes->emptyTypePack,
|
||||
genericAs
|
||||
});
|
||||
const TypeId genericNothingToAsType = arena.addType(FunctionType{{}, {genericAs}, builtinTypes->emptyTypePack, genericAs});
|
||||
|
||||
// { lower : string -> string }
|
||||
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) -> ()}")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(
|
||||
tbl({{"x", genericTToNothingType}}),
|
||||
tbl({{"x", genericUToNothingType}})
|
||||
);
|
||||
CHECK_IS_SUBTYPE(tbl({{"x", genericTToNothingType}}), tbl({{"x", genericUToNothingType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(
|
||||
meta({{"x", builtinTypes->numberType}}),
|
||||
meta({})
|
||||
);
|
||||
CHECK_IS_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { @metatable { x: boolean } }")
|
||||
{
|
||||
CHECK_IS_NOT_SUBTYPE(
|
||||
meta({{"x", builtinTypes->numberType}}),
|
||||
meta({{"x", builtinTypes->booleanType}})
|
||||
);
|
||||
CHECK_IS_NOT_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({{"x", builtinTypes->booleanType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <!: { @metatable { x: boolean } }")
|
||||
{
|
||||
CHECK_IS_NOT_SUBTYPE(
|
||||
meta({}),
|
||||
meta({{"x", builtinTypes->booleanType}})
|
||||
);
|
||||
CHECK_IS_NOT_SUBTYPE(meta({}), meta({{"x", builtinTypes->booleanType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <: {}")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(
|
||||
meta({}),
|
||||
tbl({})
|
||||
);
|
||||
CHECK_IS_SUBTYPE(meta({}), tbl({}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { u: boolean }, x: number } <: { x: number }")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(
|
||||
meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}),
|
||||
tbl({{"x", builtinTypes->numberType}})
|
||||
);
|
||||
CHECK_IS_SUBTYPE(meta({{"u", builtinTypes->booleanType}}, {{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->numberType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { x: number }")
|
||||
{
|
||||
CHECK_IS_NOT_SUBTYPE(
|
||||
meta({{"x", builtinTypes->numberType}}),
|
||||
tbl({{"x", builtinTypes->numberType}})
|
||||
);
|
||||
CHECK_IS_NOT_SUBTYPE(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")
|
||||
{
|
||||
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}")
|
||||
{
|
||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt)
|
||||
{
|
||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||
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});
|
||||
});
|
||||
|
||||
@ -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}")
|
||||
{
|
||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt)
|
||||
{
|
||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||
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});
|
||||
});
|
||||
|
||||
@ -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}")
|
||||
{
|
||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt)
|
||||
{
|
||||
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
||||
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});
|
||||
});
|
||||
|
||||
@ -960,6 +928,50 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(string) -> number <: ~fun & (string) -> numb
|
||||
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 can be bound to X.
|
||||
|
@ -1067,8 +1067,9 @@ local w = c and 1
|
||||
CHECK("false | number" == toString(requireType("z")));
|
||||
else
|
||||
CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("((false?) & a) | number" == toString(requireType("w")));
|
||||
CHECK("((false?) & unknown) | number" == toString(requireType("w")));
|
||||
else
|
||||
CHECK("(boolean | number)?" == toString(requireType("w")));
|
||||
}
|
||||
|
@ -308,7 +308,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_if_condition_position")
|
||||
else
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({3, 26})));
|
||||
CHECK_EQ("number", toString(requireTypeAtPosition({6, 26})));
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_in_assert_position")
|
||||
|
@ -3788,4 +3788,40 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_shifted_tables")
|
||||
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();
|
||||
|
@ -1428,7 +1428,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "be_sure_to_use_active_txnlog_when_evaluating
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
for (const auto& e: result.errors)
|
||||
for (const auto& e : result.errors)
|
||||
CHECK(5 == e.location.begin.line);
|
||||
}
|
||||
|
||||
|
@ -81,18 +81,12 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U")
|
||||
|
||||
TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
|
||||
{
|
||||
TypeId stringToUnit = arena.addType(FunctionType{
|
||||
arena.addTypePack({builtinTypes.stringType}),
|
||||
arena.addTypePack({})
|
||||
});
|
||||
TypeId stringToUnit = arena.addType(FunctionType{arena.addTypePack({builtinTypes.stringType}), arena.addTypePack({})});
|
||||
|
||||
auto [x, xFree] = freshType();
|
||||
TypePackId y = arena.freshTypePack(&scope);
|
||||
|
||||
TypeId xToY = arena.addType(FunctionType{
|
||||
arena.addTypePack({x}),
|
||||
y
|
||||
});
|
||||
TypeId xToY = arena.addType(FunctionType{arena.addTypePack({x}), y});
|
||||
|
||||
u2.unify(stringToUnit, xToY);
|
||||
|
||||
@ -105,4 +99,54 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "(string) -> () <: (X) -> Y...")
|
||||
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();
|
||||
|
@ -3,7 +3,6 @@ AnnotationTests.two_type_params
|
||||
AstQuery.last_argument_function_call_type
|
||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||
AutocompleteTest.autocomplete_if_else_regression
|
||||
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||
AutocompleteTest.autocomplete_oop_implicit_self
|
||||
AutocompleteTest.autocomplete_response_perf1
|
||||
@ -92,8 +91,6 @@ IntersectionTypes.table_intersection_write_sealed_indirect
|
||||
IntersectionTypes.table_write_sealed_indirect
|
||||
Normalize.negations_of_tables
|
||||
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.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
||||
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_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||
TableTests.call_method
|
||||
TableTests.call_method_with_explicit_self_argument
|
||||
TableTests.cannot_augment_sealed_table
|
||||
TableTests.cannot_change_type_of_unsealed_table_prop
|
||||
TableTests.casting_sealed_tables_with_props_into_table_with_indexer
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer4
|
||||
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
||||
TableTests.checked_prop_too_early
|
||||
TableTests.cli_84607_missing_prop_in_array_or_dict
|
||||
TableTests.cyclic_shifted_tables
|
||||
TableTests.defining_a_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_hang_when_trying_to_look_up_in_cyclic_metatable_index
|
||||
TableTests.dont_leak_free_table_props
|
||||
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
||||
TableTests.dont_suggest_exact_match_keys
|
||||
TableTests.error_detailed_metatable_prop
|
||||
TableTests.explicitly_typed_table
|
||||
@ -154,19 +152,21 @@ TableTests.inequality_operators_imply_exactly_matching_types
|
||||
TableTests.infer_array_2
|
||||
TableTests.infer_indexer_from_value_property_in_literal
|
||||
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.instantiate_table_cloning_3
|
||||
TableTests.leaking_bad_metatable_errors
|
||||
TableTests.less_exponential_blowup_please
|
||||
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
||||
TableTests.mixed_tables_with_implicit_numbered_keys
|
||||
TableTests.ok_to_add_property_to_free_table
|
||||
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_function_call
|
||||
TableTests.only_ascribe_synthetic_names_at_module_scope
|
||||
TableTests.oop_indexer_works
|
||||
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_metatables_of_metatables_of_table
|
||||
TableTests.quantifying_a_bound_var_works
|
||||
@ -185,7 +185,6 @@ TableTests.table_unification_4
|
||||
TableTests.type_mismatch_on_massive_table_is_cut_short
|
||||
TableTests.used_colon_instead_of_dot
|
||||
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.wrong_assign_does_hit_indexer
|
||||
ToDot.function
|
||||
@ -215,6 +214,7 @@ TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
||||
TypeFamilyTests.family_as_fn_arg
|
||||
TypeFamilyTests.table_internal_families
|
||||
TypeFamilyTests.unsolvable_family
|
||||
TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
|
||||
TypeInfer.bidirectional_checking_of_higher_order_function
|
||||
TypeInfer.check_type_infer_recursion_count
|
||||
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_locals_via_assignment_from_its_call_site
|
||||
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.tc_after_error_recovery_no_replacement_name_in_error
|
||||
TypeInfer.type_infer_cache_limit_normalizer
|
||||
TypeInfer.type_infer_recursion_limit_no_ice
|
||||
TypeInfer.type_infer_recursion_limit_normalizer
|
||||
TypeInferAnyError.can_subscript_any
|
||||
TypeInferAnyError.for_in_loop_iterator_is_any
|
||||
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_any2
|
||||
TypeInferAnyError.intersection_of_any_can_have_props
|
||||
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
||||
TypeInferAnyError.union_of_types_regression_test
|
||||
TypeInferClasses.can_read_prop_of_base_class_using_string
|
||||
TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||
TypeInferClasses.index_instance_property
|
||||
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
||||
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
|
||||
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.improved_function_arg_mismatch_errors
|
||||
TypeInferFunctions.infer_anonymous_function_arguments
|
||||
TypeInferFunctions.infer_anonymous_function_arguments_outside_call
|
||||
TypeInferFunctions.infer_generic_function_function_argument
|
||||
TypeInferFunctions.infer_generic_function_function_argument_overloaded
|
||||
TypeInferFunctions.infer_generic_lib_function_function_argument
|
||||
TypeInferFunctions.infer_anonymous_function_arguments_outside_call
|
||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||
TypeInferFunctions.luau_subtyping_is_np_hard
|
||||
TypeInferFunctions.no_lossy_function_type
|
||||
@ -269,21 +272,22 @@ TypeInferFunctions.too_few_arguments_variadic_generic2
|
||||
TypeInferFunctions.too_many_arguments_error_location
|
||||
TypeInferFunctions.too_many_return_values_in_parentheses
|
||||
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.dcr_iteration_on_never_gives_never
|
||||
TypeInferLoops.for_in_loop
|
||||
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_incompatible_args_to_iterator
|
||||
TypeInferLoops.for_in_loop_with_next
|
||||
TypeInferLoops.ipairs_produces_integral_indices
|
||||
TypeInferLoops.iteration_regression_issue_69967_alt
|
||||
TypeInferLoops.loop_iter_basic
|
||||
TypeInferLoops.loop_iter_metamethod_nil
|
||||
TypeInferLoops.loop_iter_metamethod_ok_with_inference
|
||||
TypeInferLoops.loop_iter_trailing_nil
|
||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||
TypeInferModules.do_not_modify_imported_types_5
|
||||
TypeInferModules.module_type_conflict
|
||||
TypeInferModules.module_type_conflict_instantiated
|
||||
@ -294,7 +298,6 @@ TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||
TypeInferOOP.methods_are_topologically_sorted
|
||||
TypeInferOperators.and_binexps_dont_unify
|
||||
TypeInferOperators.cli_38355_recursive_union
|
||||
TypeInferOperators.compound_assign_mismatch_metatable
|
||||
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||
TypeInferOperators.luau_polyfill_is_array
|
||||
|
Loading…
Reference in New Issue
Block a user