2023-08-18 19:15:41 +01:00
|
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
|
|
|
|
|
|
#include "Luau/Subtyping.h"
|
|
|
|
|
|
|
|
|
|
#include "Luau/Common.h"
|
2023-08-25 18:23:55 +01:00
|
|
|
|
#include "Luau/Error.h"
|
2023-08-18 19:15:41 +01:00
|
|
|
|
#include "Luau/Normalize.h"
|
2023-09-15 18:26:59 +01:00
|
|
|
|
#include "Luau/Scope.h"
|
2023-08-25 18:23:55 +01:00
|
|
|
|
#include "Luau/StringUtils.h"
|
|
|
|
|
#include "Luau/ToString.h"
|
2023-08-18 19:15:41 +01:00
|
|
|
|
#include "Luau/Type.h"
|
2023-08-25 18:23:55 +01:00
|
|
|
|
#include "Luau/TypeArena.h"
|
2023-08-18 19:15:41 +01:00
|
|
|
|
#include "Luau/TypePack.h"
|
|
|
|
|
#include "Luau/TypeUtils.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
|
{
|
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
struct VarianceFlipper
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
2023-08-25 18:23:55 +01:00
|
|
|
|
Subtyping::Variance* variance;
|
|
|
|
|
Subtyping::Variance oldValue;
|
|
|
|
|
|
|
|
|
|
VarianceFlipper(Subtyping::Variance* v)
|
|
|
|
|
: variance(v)
|
|
|
|
|
, oldValue(*v)
|
|
|
|
|
{
|
|
|
|
|
switch (oldValue)
|
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
case Subtyping::Variance::Covariant:
|
|
|
|
|
*variance = Subtyping::Variance::Contravariant;
|
|
|
|
|
break;
|
|
|
|
|
case Subtyping::Variance::Contravariant:
|
|
|
|
|
*variance = Subtyping::Variance::Covariant;
|
|
|
|
|
break;
|
2023-08-25 18:23:55 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~VarianceFlipper()
|
|
|
|
|
{
|
|
|
|
|
*variance = oldValue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other)
|
2023-08-25 18:23:55 +01:00
|
|
|
|
{
|
|
|
|
|
isSubtype &= other.isSubtype;
|
|
|
|
|
// `|=` is intentional here, we want to preserve error related flags.
|
|
|
|
|
isErrorSuppressing |= other.isErrorSuppressing;
|
|
|
|
|
normalizationTooComplex |= other.normalizationTooComplex;
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return *this;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
2023-08-25 18:23:55 +01:00
|
|
|
|
isSubtype |= other.isSubtype;
|
|
|
|
|
isErrorSuppressing |= other.isErrorSuppressing;
|
|
|
|
|
normalizationTooComplex |= other.normalizationTooComplex;
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return *this;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 18:58:27 +01:00
|
|
|
|
SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
|
|
|
|
|
{
|
|
|
|
|
return SubtypingResult{
|
|
|
|
|
!result.isSubtype,
|
|
|
|
|
result.isErrorSuppressing,
|
|
|
|
|
result.normalizationTooComplex,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
SubtypingResult SubtypingResult::all(const std::vector<SubtypingResult>& results)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
2023-08-25 18:23:55 +01:00
|
|
|
|
SubtypingResult acc{true, false};
|
|
|
|
|
for (const SubtypingResult& current : results)
|
|
|
|
|
acc.andAlso(current);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
return acc;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
SubtypingResult SubtypingResult::any(const std::vector<SubtypingResult>& results)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
2023-08-25 18:23:55 +01:00
|
|
|
|
SubtypingResult acc{false, false};
|
|
|
|
|
for (const SubtypingResult& current : results)
|
|
|
|
|
acc.orElse(current);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
return acc;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
|
|
|
|
{
|
|
|
|
|
mappedGenerics.clear();
|
|
|
|
|
mappedGenericPacks.clear();
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult result = isCovariantWith(subTy, superTy);
|
2023-08-25 18:23:55 +01:00
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
for (const auto& [subTy, bounds] : mappedGenerics)
|
2023-08-25 18:23:55 +01:00
|
|
|
|
{
|
|
|
|
|
const auto& lb = bounds.lowerBound;
|
|
|
|
|
const auto& ub = bounds.upperBound;
|
|
|
|
|
|
|
|
|
|
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
|
|
|
|
|
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.andAlso(isCovariantWith(lowerBound, upperBound));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp)
|
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(subTp, superTp);
|
2023-08-25 18:23:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
struct SeenSetPopper
|
|
|
|
|
{
|
|
|
|
|
Subtyping::SeenSet* seenTypes;
|
|
|
|
|
std::pair<TypeId, TypeId> pair;
|
|
|
|
|
|
|
|
|
|
SeenSetPopper(Subtyping::SeenSet* seenTypes, std::pair<TypeId, TypeId> pair)
|
|
|
|
|
: seenTypes(seenTypes)
|
|
|
|
|
, pair(pair)
|
2023-09-15 18:26:59 +01:00
|
|
|
|
{
|
|
|
|
|
}
|
2023-08-25 18:23:55 +01:00
|
|
|
|
|
|
|
|
|
~SeenSetPopper()
|
|
|
|
|
{
|
|
|
|
|
seenTypes->erase(pair);
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-09-15 18:26:59 +01:00
|
|
|
|
} // namespace
|
2023-08-25 18:23:55 +01:00
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, TypeId superTy)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
subTy = follow(subTy);
|
|
|
|
|
superTy = follow(superTy);
|
|
|
|
|
|
|
|
|
|
// TODO: Do we care about returning a proof that this is error-suppressing?
|
|
|
|
|
// e.g. given `a | error <: a | error` where both operands are pointer equal,
|
|
|
|
|
// then should it also carry the information that it's error-suppressing?
|
|
|
|
|
// If it should, then `error <: error` should also do the same.
|
|
|
|
|
if (subTy == superTy)
|
|
|
|
|
return {true};
|
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
std::pair<TypeId, TypeId> typePair{subTy, superTy};
|
|
|
|
|
if (!seenTypes.insert(typePair).second)
|
|
|
|
|
return {true};
|
|
|
|
|
|
|
|
|
|
SeenSetPopper ssp{&seenTypes, typePair};
|
2023-08-18 19:15:41 +01:00
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
// 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);
|
|
|
|
|
|
2023-09-01 18:58:27 +01:00
|
|
|
|
if (auto subUnion = get<UnionType>(subTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(subUnion, superTy);
|
2023-09-01 18:58:27 +01:00
|
|
|
|
else if (auto superUnion = get<UnionType>(superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(subTy, superUnion);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
else if (auto superIntersection = get<IntersectionType>(superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(subTy, superIntersection);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
else if (auto subIntersection = get<IntersectionType>(subTy))
|
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult result = isCovariantWith(subIntersection, superTy);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex)
|
|
|
|
|
return result;
|
|
|
|
|
else
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(normalizer->normalize(subTy), normalizer->normalize(superTy));
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
else if (get<AnyType>(superTy))
|
|
|
|
|
return {true}; // This is always true.
|
|
|
|
|
else if (get<AnyType>(subTy))
|
|
|
|
|
{
|
|
|
|
|
// any = unknown | error, so we rewrite this to match.
|
|
|
|
|
// As per TAPL: A | B <: T iff A <: T && B <: T
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(builtinTypes->unknownType, superTy).andAlso(isCovariantWith(builtinTypes->errorType, superTy));
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
2023-08-25 18:23:55 +01:00
|
|
|
|
else if (get<UnknownType>(superTy))
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
LUAU_ASSERT(!get<AnyType>(subTy)); // TODO: replace with ice.
|
|
|
|
|
LUAU_ASSERT(!get<UnionType>(subTy)); // TODO: replace with ice.
|
2023-08-18 19:15:41 +01:00
|
|
|
|
LUAU_ASSERT(!get<IntersectionType>(subTy)); // TODO: replace with ice.
|
|
|
|
|
|
|
|
|
|
bool errorSuppressing = get<ErrorType>(subTy);
|
|
|
|
|
return {!errorSuppressing, errorSuppressing};
|
|
|
|
|
}
|
|
|
|
|
else if (get<NeverType>(subTy))
|
|
|
|
|
return {true};
|
|
|
|
|
else if (get<ErrorType>(superTy))
|
|
|
|
|
return {false, true};
|
|
|
|
|
else if (get<ErrorType>(subTy))
|
|
|
|
|
return {false, true};
|
2023-09-15 18:26:59 +01:00
|
|
|
|
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);
|
2023-08-25 18:23:55 +01:00
|
|
|
|
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
|
|
|
|
|
{
|
|
|
|
|
bool ok = bindGeneric(subTy, superTy);
|
|
|
|
|
return {ok};
|
|
|
|
|
}
|
|
|
|
|
else if (auto superGeneric = get<GenericType>(superTy); superGeneric && variance == Variance::Contravariant)
|
|
|
|
|
{
|
|
|
|
|
bool ok = bindGeneric(subTy, superTy);
|
|
|
|
|
return {ok};
|
|
|
|
|
}
|
2023-08-18 19:15:41 +01:00
|
|
|
|
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-08-25 18:23:55 +01:00
|
|
|
|
else if (auto p = get2<TableType, TableType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-09-01 18:58:27 +01:00
|
|
|
|
else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-09-01 18:58:27 +01:00
|
|
|
|
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-09-01 18:58:27 +01:00
|
|
|
|
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-09-01 18:58:27 +01:00
|
|
|
|
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-09-01 18:58:27 +01:00
|
|
|
|
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-09-01 18:58:27 +01:00
|
|
|
|
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(p);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
|
|
|
|
|
return {false};
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
subTp = follow(subTp);
|
|
|
|
|
superTp = follow(superTp);
|
|
|
|
|
|
|
|
|
|
auto [subHead, subTail] = flatten(subTp);
|
|
|
|
|
auto [superHead, superTail] = flatten(superTp);
|
|
|
|
|
|
|
|
|
|
const size_t headSize = std::min(subHead.size(), superHead.size());
|
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
std::vector<SubtypingResult> results;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
results.reserve(std::max(subHead.size(), superHead.size()) + 1);
|
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
if (subTp == superTp)
|
|
|
|
|
return {true};
|
|
|
|
|
|
2023-08-18 19:15:41 +01:00
|
|
|
|
// Match head types pairwise
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < headSize; ++i)
|
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
results.push_back(isCovariantWith(subHead[i], superHead[i]));
|
2023-08-18 19:15:41 +01:00
|
|
|
|
if (!results.back().isSubtype)
|
|
|
|
|
return {false};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle mismatched head sizes
|
|
|
|
|
|
|
|
|
|
if (subHead.size() < superHead.size())
|
|
|
|
|
{
|
|
|
|
|
if (subTail)
|
|
|
|
|
{
|
|
|
|
|
if (auto vt = get<VariadicTypePack>(*subTail))
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = headSize; i < superHead.size(); ++i)
|
2023-09-15 18:26:59 +01:00
|
|
|
|
results.push_back(isCovariantWith(vt->ty, superHead[i]));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
}
|
|
|
|
|
else if (auto gt = get<GenericTypePack>(*subTail))
|
|
|
|
|
{
|
|
|
|
|
if (variance == Variance::Covariant)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
2023-08-25 18:23:55 +01:00
|
|
|
|
// For any non-generic type T:
|
|
|
|
|
//
|
|
|
|
|
// <X>(X) -> () <: (T) -> ()
|
|
|
|
|
|
|
|
|
|
// Possible optimization: If headSize == 0 then we can just use subTp as-is.
|
2023-09-15 18:26:59 +01:00
|
|
|
|
std::vector<TypeId> headSlice(begin(superHead), begin(superHead) + headSize);
|
2023-08-25 18:23:55 +01:00
|
|
|
|
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
|
|
|
|
|
|
|
|
|
|
if (TypePackId* other = mappedGenericPacks.find(*subTail))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
results.push_back(isCovariantWith(*other, superTailPack));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
else
|
|
|
|
|
mappedGenericPacks.try_insert(*subTail, superTailPack);
|
|
|
|
|
|
|
|
|
|
// FIXME? Not a fan of the early return here. It makes the
|
|
|
|
|
// control flow harder to reason about.
|
|
|
|
|
return SubtypingResult::all(results);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// For any non-generic type T:
|
|
|
|
|
//
|
|
|
|
|
// (T) -> () </: <X>(X) -> ()
|
|
|
|
|
//
|
|
|
|
|
return {false};
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2023-08-25 18:23:55 +01:00
|
|
|
|
unexpected(*subTail);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return {false};
|
|
|
|
|
}
|
|
|
|
|
else if (subHead.size() > superHead.size())
|
|
|
|
|
{
|
|
|
|
|
if (superTail)
|
|
|
|
|
{
|
|
|
|
|
if (auto vt = get<VariadicTypePack>(*superTail))
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = headSize; i < subHead.size(); ++i)
|
2023-09-15 18:26:59 +01:00
|
|
|
|
results.push_back(isCovariantWith(subHead[i], vt->ty));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
}
|
|
|
|
|
else if (auto gt = get<GenericTypePack>(*superTail))
|
|
|
|
|
{
|
|
|
|
|
if (variance == Variance::Contravariant)
|
|
|
|
|
{
|
|
|
|
|
// For any non-generic type T:
|
|
|
|
|
//
|
|
|
|
|
// <X...>(X...) -> () <: (T) -> ()
|
|
|
|
|
|
|
|
|
|
// Possible optimization: If headSize == 0 then we can just use subTp as-is.
|
2023-09-15 18:26:59 +01:00
|
|
|
|
std::vector<TypeId> headSlice(begin(subHead), begin(subHead) + headSize);
|
2023-08-25 18:23:55 +01:00
|
|
|
|
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
|
|
|
|
|
|
|
|
|
|
if (TypePackId* other = mappedGenericPacks.find(*superTail))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
results.push_back(isCovariantWith(*other, subTailPack));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
else
|
|
|
|
|
mappedGenericPacks.try_insert(*superTail, subTailPack);
|
|
|
|
|
|
|
|
|
|
// FIXME? Not a fan of the early return here. It makes the
|
|
|
|
|
// control flow harder to reason about.
|
|
|
|
|
return SubtypingResult::all(results);
|
|
|
|
|
}
|
|
|
|
|
else
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
2023-08-25 18:23:55 +01:00
|
|
|
|
// For any non-generic type T:
|
|
|
|
|
//
|
|
|
|
|
// () -> T </: <X...>() -> X...
|
|
|
|
|
return {false};
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2023-08-25 18:23:55 +01:00
|
|
|
|
unexpected(*superTail);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return {false};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle tails
|
|
|
|
|
|
|
|
|
|
if (subTail && superTail)
|
|
|
|
|
{
|
|
|
|
|
if (auto p = get2<VariadicTypePack, VariadicTypePack>(*subTail, *superTail))
|
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
results.push_back(isCovariantWith(p));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
}
|
|
|
|
|
else if (auto p = get2<GenericTypePack, GenericTypePack>(*subTail, *superTail))
|
|
|
|
|
{
|
|
|
|
|
bool ok = bindGeneric(*subTail, *superTail);
|
|
|
|
|
results.push_back({ok});
|
|
|
|
|
}
|
|
|
|
|
else if (get2<VariadicTypePack, GenericTypePack>(*subTail, *superTail))
|
|
|
|
|
{
|
|
|
|
|
if (variance == Variance::Contravariant)
|
|
|
|
|
{
|
|
|
|
|
// <A...>(A...) -> number <: (...number) -> number
|
|
|
|
|
bool ok = bindGeneric(*subTail, *superTail);
|
|
|
|
|
results.push_back({ok});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// (number) -> ...number </: <A...>(number) -> A...
|
|
|
|
|
results.push_back({false});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (get2<GenericTypePack, VariadicTypePack>(*subTail, *superTail))
|
|
|
|
|
{
|
|
|
|
|
if (variance == Variance::Contravariant)
|
|
|
|
|
{
|
|
|
|
|
// (...number) -> number </: <A...>(A...) -> number
|
|
|
|
|
results.push_back({false});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// <A...>() -> A... <: () -> ...number
|
|
|
|
|
bool ok = bindGeneric(*subTail, *superTail);
|
|
|
|
|
results.push_back({ok});
|
|
|
|
|
}
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
2023-09-15 18:26:59 +01:00
|
|
|
|
iceReporter->ice(
|
|
|
|
|
format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str()));
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
else if (subTail)
|
|
|
|
|
{
|
|
|
|
|
if (get<VariadicTypePack>(*subTail))
|
|
|
|
|
{
|
|
|
|
|
return {false};
|
|
|
|
|
}
|
2023-08-25 18:23:55 +01:00
|
|
|
|
else if (get<GenericTypePack>(*subTail))
|
|
|
|
|
{
|
|
|
|
|
bool ok = bindGeneric(*subTail, builtinTypes->emptyTypePack);
|
|
|
|
|
return {ok};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
unexpected(*subTail);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
else if (superTail)
|
|
|
|
|
{
|
|
|
|
|
if (get<VariadicTypePack>(*superTail))
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* A variadic type pack ...T can be thought of as an infinite union of finite type packs.
|
|
|
|
|
* () | (T) | (T, T) | (T, T, T) | ...
|
|
|
|
|
*
|
|
|
|
|
* And, per TAPL:
|
|
|
|
|
* T <: A | B iff T <: A or T <: B
|
|
|
|
|
*
|
|
|
|
|
* All variadic type packs are therefore supertypes of the empty type pack.
|
|
|
|
|
*/
|
|
|
|
|
}
|
2023-08-25 18:23:55 +01:00
|
|
|
|
else if (get<GenericTypePack>(*superTail))
|
|
|
|
|
{
|
|
|
|
|
if (variance == Variance::Contravariant)
|
|
|
|
|
{
|
|
|
|
|
bool ok = bindGeneric(builtinTypes->emptyTypePack, *superTail);
|
|
|
|
|
results.push_back({ok});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
results.push_back({false});
|
|
|
|
|
}
|
2023-08-18 19:15:41 +01:00
|
|
|
|
else
|
|
|
|
|
LUAU_ASSERT(0); // TODO
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
return SubtypingResult::all(results);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename SubTy, typename SuperTy>
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isContravariantWith(SubTy&& subTy, SuperTy&& superTy)
|
|
|
|
|
{
|
|
|
|
|
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)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(pair).andAlso(isContravariantWith(pair));
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* This is much simpler than the Unifier implementation because we don't
|
|
|
|
|
* actually care about potential "cross-talk" between union parts that match the
|
|
|
|
|
* left side.
|
|
|
|
|
*
|
|
|
|
|
* In fact, we're very limited in what we can do: If multiple choices match, but
|
|
|
|
|
* all of them have non-overlapping constraints, then we're stuck with an "or"
|
|
|
|
|
* conjunction of constraints. Solving this in the general case is quite
|
|
|
|
|
* difficult.
|
|
|
|
|
*
|
|
|
|
|
* For example, we cannot dispatch anything from this constraint:
|
|
|
|
|
*
|
|
|
|
|
* {x: number, y: string} <: {x: number, y: 'a} | {x: 'b, y: string}
|
|
|
|
|
*
|
|
|
|
|
* From this constraint, we can know that either string <: 'a or number <: 'b,
|
|
|
|
|
* but we don't know which!
|
|
|
|
|
*
|
|
|
|
|
* However:
|
|
|
|
|
*
|
|
|
|
|
* {x: number, y: string} <: {x: number, y: 'a} | {x: number, y: string}
|
|
|
|
|
*
|
|
|
|
|
* We can dispatch this constraint because there is no 'or' conjunction. One of
|
|
|
|
|
* the arms requires 0 matches.
|
|
|
|
|
*
|
|
|
|
|
* {x: number, y: string, z: boolean} | {x: number, y: 'a, z: 'b} | {x: number,
|
|
|
|
|
* y: string, z: 'b}
|
|
|
|
|
*
|
|
|
|
|
* Here, we have two matches. One asks for string ~ 'a and boolean ~ 'b. The
|
|
|
|
|
* other just asks for boolean ~ 'b. We can dispatch this and only commit
|
|
|
|
|
* boolean ~ 'b. This constraint does not teach us anything about 'a.
|
|
|
|
|
*/
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, const UnionType* superUnion)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
// As per TAPL: T <: A | B iff T <: A || T <: B
|
2023-08-25 18:23:55 +01:00
|
|
|
|
std::vector<SubtypingResult> subtypings;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
for (TypeId ty : superUnion)
|
2023-09-15 18:26:59 +01:00
|
|
|
|
subtypings.push_back(isCovariantWith(subTy, ty));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
return SubtypingResult::any(subtypings);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const UnionType* subUnion, TypeId superTy)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
// As per TAPL: A | B <: T iff A <: T && B <: T
|
2023-08-25 18:23:55 +01:00
|
|
|
|
std::vector<SubtypingResult> subtypings;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
for (TypeId ty : subUnion)
|
2023-09-15 18:26:59 +01:00
|
|
|
|
subtypings.push_back(isCovariantWith(ty, superTy));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
return SubtypingResult::all(subtypings);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, const IntersectionType* superIntersection)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
// As per TAPL: T <: A & B iff T <: A && T <: B
|
2023-08-25 18:23:55 +01:00
|
|
|
|
std::vector<SubtypingResult> subtypings;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
for (TypeId ty : superIntersection)
|
2023-09-15 18:26:59 +01:00
|
|
|
|
subtypings.push_back(isCovariantWith(subTy, ty));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
return SubtypingResult::all(subtypings);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const IntersectionType* subIntersection, TypeId superTy)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
// As per TAPL: A & B <: T iff A <: T || B <: T
|
2023-08-25 18:23:55 +01:00
|
|
|
|
std::vector<SubtypingResult> subtypings;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
for (TypeId ty : subIntersection)
|
2023-09-15 18:26:59 +01:00
|
|
|
|
subtypings.push_back(isCovariantWith(ty, superTy));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
return SubtypingResult::any(subtypings);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
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)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
return {subPrim->type == superPrim->type};
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const PrimitiveType* superPrim)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
if (get<StringSingleton>(subSingleton) && superPrim->type == PrimitiveType::String)
|
|
|
|
|
return {true};
|
|
|
|
|
else if (get<BooleanSingleton>(subSingleton) && superPrim->type == PrimitiveType::Boolean)
|
|
|
|
|
return {true};
|
|
|
|
|
else
|
|
|
|
|
return {false};
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const SingletonType* superSingleton)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
return {*subSingleton == *superSingleton};
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const TableType* subTable, const TableType* superTable)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
2023-08-25 18:23:55 +01:00
|
|
|
|
SubtypingResult result{true};
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
for (const auto& [name, prop] : superTable->props)
|
2023-08-25 18:23:55 +01:00
|
|
|
|
{
|
|
|
|
|
auto it = subTable->props.find(name);
|
|
|
|
|
if (it != subTable->props.end())
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.andAlso(isInvariantWith(prop.type(), it->second.type()));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
else
|
|
|
|
|
return SubtypingResult{false};
|
|
|
|
|
}
|
2023-08-18 19:15:41 +01:00
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
return result;
|
2023-08-18 19:15:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const MetatableType* superMt)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
|
|
|
|
return SubtypingResult::all({
|
2023-09-15 18:26:59 +01:00
|
|
|
|
isCovariantWith(subMt->table, superMt->table),
|
|
|
|
|
isCovariantWith(subMt->metatable, superMt->metatable),
|
2023-09-01 18:58:27 +01:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const TableType* superTable)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
if (auto subTable = get<TableType>(subMt->table))
|
|
|
|
|
{
|
2023-09-01 18:58:27 +01:00
|
|
|
|
// 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.
|
|
|
|
|
//
|
|
|
|
|
// There's a flaw here in that if the __index metamethod contributes a new
|
|
|
|
|
// field that would satisfy the subtyping relationship, we'll erronously say
|
|
|
|
|
// 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.
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(subTable, superTable);
|
2023-09-01 18:58:27 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// TODO: This may be a case we actually hit?
|
|
|
|
|
return {false};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const ClassType* superClass)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
|
|
|
|
return {isSubclass(subClass, superClass)};
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const TableType* superTable)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
|
|
|
|
SubtypingResult result{true};
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
for (const auto& [name, prop] : superTable->props)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
|
|
|
|
if (auto classProp = lookupClassProp(subClass, name))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.andAlso(isInvariantWith(prop.type(), classProp->type()));
|
2023-09-01 18:58:27 +01:00
|
|
|
|
else
|
|
|
|
|
return SubtypingResult{false};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction)
|
2023-08-25 18:23:55 +01:00
|
|
|
|
{
|
|
|
|
|
SubtypingResult result;
|
|
|
|
|
{
|
|
|
|
|
VarianceFlipper vf{&variance};
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.orElse(isContravariantWith(subFunction->argTypes, superFunction->argTypes));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.andAlso(isCovariantWith(subFunction->retTypes, superFunction->retTypes));
|
2023-08-25 18:23:55 +01:00
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
|
|
|
|
SubtypingResult result{false};
|
|
|
|
|
if (subPrim->type == PrimitiveType::String)
|
|
|
|
|
{
|
|
|
|
|
if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes))
|
|
|
|
|
{
|
|
|
|
|
if (auto mttv = get<TableType>(follow(metatable)))
|
|
|
|
|
{
|
|
|
|
|
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
|
|
|
|
|
{
|
|
|
|
|
if (auto stringTable = get<TableType>(it->second.type()))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.orElse(isCovariantWith(stringTable, superTable));
|
2023-09-01 18:58:27 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const TableType* superTable)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
|
|
|
|
SubtypingResult result{false};
|
|
|
|
|
if (auto stringleton = get<StringSingleton>(subSingleton))
|
|
|
|
|
{
|
|
|
|
|
if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes))
|
|
|
|
|
{
|
|
|
|
|
if (auto mttv = get<TableType>(follow(metatable)))
|
|
|
|
|
{
|
|
|
|
|
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
|
|
|
|
|
{
|
|
|
|
|
if (auto stringTable = get<TableType>(it->second.type()))
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.orElse(isCovariantWith(stringTable, superTable));
|
2023-09-01 18:58:27 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm)
|
2023-08-18 19:15:41 +01:00
|
|
|
|
{
|
|
|
|
|
if (!subNorm || !superNorm)
|
|
|
|
|
return {false, true, true};
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
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);
|
2023-08-18 19:15:41 +01:00
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const NormalizedClassType& superClass)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
|
|
|
|
for (const auto& [subClassTy, _] : subClass.classes)
|
|
|
|
|
{
|
|
|
|
|
SubtypingResult result;
|
|
|
|
|
|
|
|
|
|
for (const auto& [superClassTy, superNegations] : superClass.classes)
|
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.orElse(isCovariantWith(subClassTy, superClassTy));
|
2023-09-01 18:58:27 +01:00
|
|
|
|
if (!result.isSubtype)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
for (TypeId negation : superNegations)
|
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.andAlso(SubtypingResult::negate(isCovariantWith(subClassTy, negation)));
|
2023-09-01 18:58:27 +01:00
|
|
|
|
if (result.isSubtype)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
if (!result.isSubtype)
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {true};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const TypeIds& superTables)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& [subClassTy, _] : subClass.classes)
|
|
|
|
|
{
|
|
|
|
|
SubtypingResult result;
|
2023-09-01 18:58:27 +01:00
|
|
|
|
|
|
|
|
|
for (TypeId superTableTy : superTables)
|
2023-09-15 18:26:59 +01:00
|
|
|
|
result.orElse(isCovariantWith(subClassTy, superTableTy));
|
2023-09-01 18:58:27 +01:00
|
|
|
|
|
|
|
|
|
if (!result.isSubtype)
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {true};
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
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)
|
2023-09-08 01:13:49 +01:00
|
|
|
|
{
|
|
|
|
|
if (subFunction.isNever())
|
|
|
|
|
return {true};
|
|
|
|
|
else if (superFunction.isTop)
|
|
|
|
|
return {true};
|
|
|
|
|
else
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(subFunction.parts, superFunction.parts);
|
2023-09-08 01:13:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const TypeIds& subTypes, const TypeIds& superTypes)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
|
|
|
|
std::vector<SubtypingResult> results;
|
|
|
|
|
|
|
|
|
|
for (TypeId subTy : subTypes)
|
|
|
|
|
{
|
|
|
|
|
results.emplace_back();
|
|
|
|
|
for (TypeId superTy : superTypes)
|
2023-09-15 18:26:59 +01:00
|
|
|
|
results.back().orElse(isCovariantWith(subTy, superTy));
|
2023-09-01 18:58:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SubtypingResult::all(results);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
SubtypingResult Subtyping::isCovariantWith(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic)
|
2023-09-01 18:58:27 +01:00
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
|
return isCovariantWith(subVariadic->ty, superVariadic->ty);
|
2023-09-01 18:58:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 18:23:55 +01:00
|
|
|
|
bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy)
|
|
|
|
|
{
|
|
|
|
|
if (variance == Variance::Covariant)
|
|
|
|
|
{
|
|
|
|
|
if (!get<GenericType>(subTy))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
mappedGenerics[subTy].upperBound.insert(superTy);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!get<GenericType>(superTy))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
mappedGenerics[superTy].lowerBound.insert(subTy);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If, when performing a subtyping test, we encounter a generic on the left
|
|
|
|
|
* side, it is permissible to tentatively bind that generic to the right side
|
|
|
|
|
* type.
|
|
|
|
|
*/
|
|
|
|
|
bool Subtyping::bindGeneric(TypePackId subTp, TypePackId superTp)
|
|
|
|
|
{
|
|
|
|
|
if (variance == Variance::Contravariant)
|
|
|
|
|
std::swap(superTp, subTp);
|
|
|
|
|
|
|
|
|
|
if (!get<GenericTypePack>(subTp))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (TypePackId* m = mappedGenericPacks.find(subTp))
|
|
|
|
|
return *m == superTp;
|
|
|
|
|
|
|
|
|
|
mappedGenericPacks[subTp] = superTp;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
|
template<typename T, typename Container>
|
2023-08-25 18:23:55 +01:00
|
|
|
|
TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
|
|
|
|
|
{
|
|
|
|
|
if (container.empty())
|
|
|
|
|
return orElse;
|
|
|
|
|
else if (container.size() == 1)
|
|
|
|
|
return *begin(container);
|
|
|
|
|
else
|
|
|
|
|
return arena->addType(T{std::vector<TypeId>(begin(container), end(container))});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Subtyping::unexpected(TypePackId tp)
|
|
|
|
|
{
|
|
|
|
|
iceReporter->ice(format("Unexpected type pack %s", toString(tp).c_str()));
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-18 19:15:41 +01:00
|
|
|
|
} // namespace Luau
|