mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
3b0e93bec9
# What's changed? Add program argument passing to scripts run using the Luau REPL! You can now pass `--program-args` (or shorthand `-a`) to the REPL which will treat all remaining arguments as arguments to pass to executed scripts. These values can be accessed through variadic argument expansion. You can read these values like so: ``` local args = {...} -- gets you an array of all the arguments ``` For example if we run the following script like `luau test.lua -a test1 test2 test3`: ``` -- test.lua print(...) ``` you should get the output: ``` test1 test2 test3 ``` ### Native Code Generation * Improve A64 lowering for vector operations by using vector instructions * Fix lowering issue in IR value location tracking! - A developer reported a divergence between code run in the VM and Native Code Generation which we have now fixed ### New Type Solver * Apply substitution to type families, and emit new constraints to reduce those further * More progress on reducing comparison (`lt/le`)type families * Resolve two major sources of cyclic types in the new solver ### Miscellaneous * Turned internal compiler errors (ICE's) into warnings and errors ------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
1432 lines
54 KiB
C++
1432 lines
54 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
#include "Luau/TypeFwd.h"
|
|
#include "Luau/TypePath.h"
|
|
|
|
#include "Luau/Normalize.h"
|
|
#include "Luau/Subtyping.h"
|
|
#include "Luau/Type.h"
|
|
#include "Luau/TypePack.h"
|
|
#include "Luau/TypeFamily.h"
|
|
|
|
#include "doctest.h"
|
|
#include "Fixture.h"
|
|
#include "RegisterCallbacks.h"
|
|
|
|
#include <initializer_list>
|
|
|
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
|
|
|
using namespace Luau;
|
|
|
|
namespace Luau
|
|
{
|
|
|
|
std::ostream& operator<<(std::ostream& lhs, const SubtypingVariance& variance)
|
|
{
|
|
switch (variance)
|
|
{
|
|
case SubtypingVariance::Covariant:
|
|
return lhs << "covariant";
|
|
case SubtypingVariance::Contravariant:
|
|
return lhs << "contravariant";
|
|
case SubtypingVariance::Invariant:
|
|
return lhs << "invariant";
|
|
case SubtypingVariance::Invalid:
|
|
return lhs << "*invalid*";
|
|
}
|
|
|
|
return lhs;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& lhs, const SubtypingReasoning& reasoning)
|
|
{
|
|
return lhs << toString(reasoning.subPath) << " </: " << toString(reasoning.superPath) << " (" << reasoning.variance << ")";
|
|
}
|
|
|
|
bool operator==(const DenseHashSet<SubtypingReasoning, SubtypingReasoningHash>& set, const std::vector<SubtypingReasoning>& items)
|
|
{
|
|
if (items.size() != set.size())
|
|
return false;
|
|
|
|
for (const SubtypingReasoning& r : items)
|
|
{
|
|
if (!set.contains(r))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
}; // namespace Luau
|
|
|
|
struct SubtypeFixture : Fixture
|
|
{
|
|
TypeArena arena;
|
|
InternalErrorReporter iceReporter;
|
|
UnifierSharedState sharedState{&ice};
|
|
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
|
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)};
|
|
ScopePtr moduleScope{new Scope(rootScope)};
|
|
|
|
Subtyping subtyping = mkSubtyping(rootScope);
|
|
BuiltinTypeFamilies builtinTypeFamilies{};
|
|
|
|
Subtyping mkSubtyping(const ScopePtr& scope)
|
|
{
|
|
return Subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&iceReporter}, NotNull{scope.get()}};
|
|
}
|
|
|
|
TypePackId pack(std::initializer_list<TypeId> tys)
|
|
{
|
|
return arena.addTypePack(tys);
|
|
}
|
|
|
|
TypePackId pack(std::initializer_list<TypeId> tys, TypePackVariant tail)
|
|
{
|
|
return arena.addTypePack(tys, arena.addTypePack(std::move(tail)));
|
|
}
|
|
|
|
TypeId fn(std::initializer_list<TypeId> args, std::initializer_list<TypeId> rets)
|
|
{
|
|
return arena.addType(FunctionType{pack(args), pack(rets)});
|
|
}
|
|
|
|
TypeId fn(std::initializer_list<TypeId> argHead, TypePackVariant argTail, std::initializer_list<TypeId> rets)
|
|
{
|
|
return arena.addType(FunctionType{pack(argHead, std::move(argTail)), pack(rets)});
|
|
}
|
|
|
|
TypeId fn(std::initializer_list<TypeId> args, std::initializer_list<TypeId> retHead, TypePackVariant retTail)
|
|
{
|
|
return arena.addType(FunctionType{pack(args), pack(retHead, std::move(retTail))});
|
|
}
|
|
|
|
TypeId fn(std::initializer_list<TypeId> argHead, TypePackVariant argTail, std::initializer_list<TypeId> retHead, TypePackVariant retTail)
|
|
{
|
|
return arena.addType(FunctionType{pack(argHead, std::move(argTail)), pack(retHead, std::move(retTail))});
|
|
}
|
|
|
|
TypeId tbl(TableType::Props&& props)
|
|
{
|
|
return arena.addType(TableType{std::move(props), std::nullopt, {}, TableState::Sealed});
|
|
}
|
|
|
|
TypeId idx(TypeId keyTy, TypeId valueTy)
|
|
{
|
|
return arena.addType(TableType{{}, TableIndexer{keyTy, valueTy}, {}, TableState::Sealed});
|
|
}
|
|
|
|
// `&`
|
|
TypeId meet(TypeId a, TypeId b)
|
|
{
|
|
return arena.addType(IntersectionType{{a, b}});
|
|
}
|
|
|
|
// `|`
|
|
TypeId join(TypeId a, TypeId b)
|
|
{
|
|
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, ""});
|
|
}
|
|
|
|
TypeId cls(const std::string& name, ClassType::Props&& props)
|
|
{
|
|
TypeId ty = cls(name);
|
|
getMutable<ClassType>(ty)->props = std::move(props);
|
|
return ty;
|
|
}
|
|
|
|
TypeId opt(TypeId ty)
|
|
{
|
|
return join(ty, builtinTypes->nilType);
|
|
}
|
|
|
|
TypeId cyclicTable(std::function<void(TypeId, TableType*)>&& cb)
|
|
{
|
|
TypeId res = arena.addType(GenericType{});
|
|
TableType tt{};
|
|
cb(res, &tt);
|
|
emplaceType<TableType>(asMutable(res), std::move(tt));
|
|
return res;
|
|
}
|
|
|
|
TypeId meta(TableType::Props&& metaProps, TableType::Props&& tableProps = {})
|
|
{
|
|
return arena.addType(MetatableType{tbl(std::move(tableProps)), tbl(std::move(metaProps))});
|
|
}
|
|
|
|
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"});
|
|
TypePackId genericCs = arena.addTypePack(GenericTypePack{"C"});
|
|
|
|
SubtypingResult isSubtype(TypeId subTy, TypeId superTy)
|
|
{
|
|
return subtyping.isSubtype(subTy, superTy);
|
|
}
|
|
|
|
TypeId helloType = arena.addType(SingletonType{StringSingleton{"hello"}});
|
|
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);
|
|
|
|
TypeId helloAndWorldType = meet(helloType, worldType);
|
|
TypeId booleanAndTrueType = meet(builtinTypes->booleanType, builtinTypes->trueType);
|
|
|
|
/**
|
|
* class
|
|
* \- Root
|
|
* |- Child
|
|
* | |-GrandchildOne
|
|
* | \-GrandchildTwo
|
|
* \- AnotherChild
|
|
* |- AnotherGrandchildOne
|
|
* \- AnotherGrandchildTwo
|
|
*/
|
|
TypeId rootClass = cls("Root");
|
|
TypeId childClass = cls("Child", rootClass);
|
|
TypeId grandchildOneClass = cls("GrandchildOne", childClass);
|
|
TypeId grandchildTwoClass = cls("GrandchildTwo", childClass);
|
|
TypeId anotherChildClass = cls("AnotherChild", rootClass);
|
|
TypeId anotherGrandchildOneClass = cls("AnotherGrandchildOne", anotherChildClass);
|
|
TypeId anotherGrandchildTwoClass = cls("AnotherGrandchildTwo", anotherChildClass);
|
|
|
|
TypeId vec2Class = cls("Vec2", {
|
|
{"X", builtinTypes->numberType},
|
|
{"Y", builtinTypes->numberType},
|
|
});
|
|
|
|
TypeId readOnlyVec2Class = cls("ReadOnlyVec2", {
|
|
{"X", Property::readonly(builtinTypes->numberType)},
|
|
{"Y", Property::readonly(builtinTypes->numberType)},
|
|
});
|
|
|
|
// "hello" | "hello"
|
|
TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}});
|
|
|
|
// () -> ()
|
|
const TypeId nothingToNothingType = fn({}, {});
|
|
|
|
// (number) -> string
|
|
const TypeId numberToStringType = fn({builtinTypes->numberType}, {builtinTypes->stringType});
|
|
|
|
// (unknown) -> string
|
|
const TypeId unknownToStringType = fn({builtinTypes->unknownType}, {builtinTypes->stringType});
|
|
|
|
// (number) -> ()
|
|
const TypeId numberToNothingType = fn({builtinTypes->numberType}, {});
|
|
|
|
// () -> number
|
|
const TypeId nothingToNumberType = fn({}, {builtinTypes->numberType});
|
|
|
|
// (number) -> number
|
|
const TypeId numberToNumberType = fn({builtinTypes->numberType}, {builtinTypes->numberType});
|
|
|
|
// (number) -> unknown
|
|
const TypeId numberToUnknownType = fn({builtinTypes->numberType}, {builtinTypes->unknownType});
|
|
|
|
// (number) -> (string, string)
|
|
const TypeId numberToTwoStringsType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->stringType});
|
|
|
|
// (number) -> (string, unknown)
|
|
const TypeId numberToStringAndUnknownType = fn({builtinTypes->numberType}, {builtinTypes->stringType, builtinTypes->unknownType});
|
|
|
|
// (number, number) -> string
|
|
const TypeId numberNumberToStringType = fn({builtinTypes->numberType, builtinTypes->numberType}, {builtinTypes->stringType});
|
|
|
|
// (unknown, number) -> string
|
|
const TypeId unknownNumberToStringType = fn({builtinTypes->unknownType, builtinTypes->numberType}, {builtinTypes->stringType});
|
|
|
|
// (number, string) -> string
|
|
const TypeId numberAndStringToStringType = fn({builtinTypes->numberType, builtinTypes->stringType}, {builtinTypes->stringType});
|
|
|
|
// (number, ...string) -> string
|
|
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});
|
|
|
|
// (...number) -> number
|
|
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});
|
|
|
|
// <T>(T) -> T
|
|
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});
|
|
|
|
// <T>() -> T
|
|
const TypeId genericNothingToTType = arena.addType(FunctionType{{genericT}, {}, builtinTypes->emptyTypePack, arena.addTypePack({genericT})});
|
|
|
|
// <A...>(A...) -> A...
|
|
const TypeId genericAsToAsType = arena.addType(FunctionType{{}, {genericAs}, genericAs, genericAs});
|
|
|
|
// <A...>(A...) -> number
|
|
const TypeId genericAsToNumberType = arena.addType(FunctionType{{}, {genericAs}, genericAs, arena.addTypePack({builtinTypes->numberType})});
|
|
|
|
// <B...>(B...) -> B...
|
|
const TypeId genericBsToBsType = arena.addType(FunctionType{{}, {genericBs}, genericBs, genericBs});
|
|
|
|
// <B..., C...>(B...) -> C...
|
|
const TypeId genericBsToCsType = arena.addType(FunctionType{{}, {genericBs, genericCs}, genericBs, genericCs});
|
|
|
|
// <A...>() -> A...
|
|
const TypeId genericNothingToAsType = arena.addType(FunctionType{{}, {genericAs}, builtinTypes->emptyTypePack, genericAs});
|
|
|
|
// { lower : string -> string }
|
|
TypeId tableWithLower = tbl(TableType::Props{{"lower", fn({builtinTypes->stringType}, {builtinTypes->stringType})}});
|
|
// { insaneThingNoScalarHas : () -> () }
|
|
TypeId tableWithoutScalarProp = tbl(TableType::Props{{"insaneThingNoScalarHas", fn({}, {})}});
|
|
};
|
|
|
|
#define CHECK_IS_SUBTYPE(left, right) \
|
|
do \
|
|
{ \
|
|
const auto& leftTy = (left); \
|
|
const auto& rightTy = (right); \
|
|
SubtypingResult result = isSubtype(leftTy, rightTy); \
|
|
CHECK_MESSAGE(result.isSubtype, "Expected " << leftTy << " <: " << rightTy); \
|
|
} while (0)
|
|
|
|
#define CHECK_IS_NOT_SUBTYPE(left, right) \
|
|
do \
|
|
{ \
|
|
const auto& leftTy = (left); \
|
|
const auto& rightTy = (right); \
|
|
SubtypingResult result = isSubtype(leftTy, rightTy); \
|
|
CHECK_MESSAGE(!result.isSubtype, "Expected " << leftTy << " </: " << rightTy); \
|
|
} while (0)
|
|
|
|
/// Internal macro for registering a generated test case.
|
|
///
|
|
/// @param der the name of the derived fixture struct
|
|
/// @param reg the name of the registration callback, invoked immediately before
|
|
/// tests are ran to register the test
|
|
/// @param run the name of the run callback, invoked to actually run the test case
|
|
#define TEST_REGISTER(der, reg, run) \
|
|
static inline DOCTEST_NOINLINE void run() \
|
|
{ \
|
|
der fix; \
|
|
fix.test(); \
|
|
} \
|
|
static inline DOCTEST_NOINLINE void reg() \
|
|
{ \
|
|
/* we have to mark this as `static` to ensure the memory remains alive \
|
|
for the entirety of the test process */ \
|
|
static std::string name = der().testName; \
|
|
doctest::detail::regTest(doctest::detail::TestCase(run, __FILE__, __LINE__, \
|
|
doctest_detail_test_suite_ns::getCurrentTestSuite()) /* the test case's name, determined at runtime */ \
|
|
* name.c_str() /* getCurrentTestSuite() only works at static initialization \
|
|
time due to implementation details. To ensure that test cases \
|
|
are grouped where they should be, manually override the suite \
|
|
with the test_suite decorator. */ \
|
|
* doctest::test_suite("Subtyping")); \
|
|
} \
|
|
DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), addTestCallback(reg));
|
|
|
|
/// Internal macro for deriving a test case fixture. Roughly analogous to
|
|
/// DOCTEST_IMPLEMENT_FIXTURE.
|
|
///
|
|
/// @param op a function (or macro) to call that compares the subtype to
|
|
/// the supertype.
|
|
/// @param symbol the symbol to use in stringification
|
|
/// @param der the name of the derived fixture struct
|
|
/// @param left the subtype expression
|
|
/// @param right the supertype expression
|
|
#define TEST_DERIVE(op, symbol, der, left, right) \
|
|
namespace \
|
|
{ \
|
|
struct der : SubtypeFixture \
|
|
{ \
|
|
const TypeId subTy = (left); \
|
|
const TypeId superTy = (right); \
|
|
const std::string testName = toString(subTy) + " " symbol " " + toString(superTy); \
|
|
inline DOCTEST_NOINLINE void test() \
|
|
{ \
|
|
op(subTy, superTy); \
|
|
} \
|
|
}; \
|
|
TEST_REGISTER(der, DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_)); \
|
|
}
|
|
|
|
/// Generates a test that checks if a type is a subtype of another.
|
|
#define TEST_IS_SUBTYPE(left, right) TEST_DERIVE(CHECK_IS_SUBTYPE, "<:", DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), left, right)
|
|
|
|
/// Generates a test that checks if a type is _not_ a subtype of another.
|
|
/// Uses <!: instead of </: to ensure that rotest doesn't explode when it splits
|
|
/// on / characters.
|
|
#define TEST_IS_NOT_SUBTYPE(left, right) TEST_DERIVE(CHECK_IS_NOT_SUBTYPE, "<!:", DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), left, right)
|
|
|
|
TEST_SUITE_BEGIN("Subtyping");
|
|
|
|
// We would like to write </: to mean "is not a subtype," but rotest does not like that at all, so we instead use <!:
|
|
|
|
TEST_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->anyType);
|
|
TEST_IS_NOT_SUBTYPE(builtinTypes->numberType, builtinTypes->stringType);
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_reducible_sub_typefamily")
|
|
{
|
|
// add<number, number> <: number
|
|
TypeId typeFamilyNum =
|
|
arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->numberType, builtinTypes->numberType}, {}});
|
|
TypeId superTy = builtinTypes->numberType;
|
|
SubtypingResult result = isSubtype(typeFamilyNum, superTy);
|
|
CHECK(result.isSubtype);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_reducible_super_typefamily")
|
|
{
|
|
// number <: add<number, number> ~ number
|
|
TypeId typeFamilyNum =
|
|
arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->numberType, builtinTypes->numberType}, {}});
|
|
TypeId subTy = builtinTypes->numberType;
|
|
SubtypingResult result = isSubtype(subTy, typeFamilyNum);
|
|
CHECK(result.isSubtype);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_irreducible_sub_typefamily")
|
|
{
|
|
// add<string, boolean> ~ never <: number
|
|
TypeId typeFamilyNum =
|
|
arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->stringType, builtinTypes->booleanType}, {}});
|
|
TypeId superTy = builtinTypes->numberType;
|
|
SubtypingResult result = isSubtype(typeFamilyNum, superTy);
|
|
CHECK(result.isSubtype);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_irreducible_super_typefamily")
|
|
{
|
|
// number <\: add<string, boolean> ~ irreducible/never
|
|
TypeId typeFamilyNum =
|
|
arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {builtinTypes->stringType, builtinTypes->booleanType}, {}});
|
|
TypeId subTy = builtinTypes->numberType;
|
|
SubtypingResult result = isSubtype(subTy, typeFamilyNum);
|
|
CHECK(!result.isSubtype);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "basic_typefamily_with_generics")
|
|
{
|
|
// <T,U>(x: T, x: U) -> add<T,U> <: (number, number) -> number
|
|
TypeId addFamily = arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.addFamily}, {genericT, genericU}, {}});
|
|
FunctionType ft{{genericT, genericU}, {}, arena.addTypePack({genericT, genericU}), arena.addTypePack({addFamily})};
|
|
TypeId functionType = arena.addType(std::move(ft));
|
|
FunctionType superFt{arena.addTypePack({builtinTypes->numberType, builtinTypes->numberType}), arena.addTypePack({builtinTypes->numberType})};
|
|
TypeId superFunction = arena.addType(std::move(superFt));
|
|
SubtypingResult result = isSubtype(functionType, superFunction);
|
|
CHECK(result.isSubtype);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "any <!: unknown")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(builtinTypes->anyType, builtinTypes->unknownType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "number? <: unknown")
|
|
{
|
|
CHECK_IS_SUBTYPE(builtinTypes->optionalNumberType, builtinTypes->unknownType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "number <: unknown")
|
|
{
|
|
CHECK_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->unknownType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "number <: number")
|
|
{
|
|
CHECK_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->numberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "number <: number?")
|
|
{
|
|
CHECK_IS_SUBTYPE(builtinTypes->numberType, builtinTypes->optionalNumberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: string")
|
|
{
|
|
CHECK_IS_SUBTYPE(helloType, builtinTypes->stringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "string <!: \"hello\"")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(builtinTypes->stringType, helloType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: \"hello\"")
|
|
{
|
|
CHECK_IS_SUBTYPE(helloType, helloType2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "true <: boolean")
|
|
{
|
|
CHECK_IS_SUBTYPE(builtinTypes->trueType, builtinTypes->booleanType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "true <: true | false")
|
|
{
|
|
CHECK_IS_SUBTYPE(builtinTypes->trueType, trueOrFalseType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "true | false <!: true")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(trueOrFalseType, builtinTypes->trueType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "true | false <: boolean")
|
|
{
|
|
CHECK_IS_SUBTYPE(trueOrFalseType, builtinTypes->booleanType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "true | false <: true | false")
|
|
{
|
|
CHECK_IS_SUBTYPE(trueOrFalseType, trueOrFalseType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" | \"world\" <: number")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(helloOrWorldType, builtinTypes->numberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "string <!: ('hello' | 'hello')")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(builtinTypes->stringType, helloOrHelloType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "true <: boolean & true")
|
|
{
|
|
CHECK_IS_SUBTYPE(builtinTypes->trueType, booleanAndTrueType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "boolean & true <: true")
|
|
{
|
|
CHECK_IS_SUBTYPE(booleanAndTrueType, builtinTypes->trueType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "boolean & true <: boolean & true")
|
|
{
|
|
CHECK_IS_SUBTYPE(booleanAndTrueType, booleanAndTrueType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" & \"world\" <: number")
|
|
{
|
|
CHECK_IS_SUBTYPE(helloAndWorldType, builtinTypes->numberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "false <!: boolean & true")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(builtinTypes->falseType, booleanAndTrueType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(unknown) -> string <: (number) -> string")
|
|
{
|
|
CHECK_IS_SUBTYPE(unknownToStringType, numberToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> string <!: (unknown) -> string")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberToStringType, unknownToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number, number) -> string <!: (number) -> string")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberNumberToStringType, numberToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> string <!: (number, number) -> string")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberToStringType, numberNumberToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number, number) -> string <!: (unknown, number) -> string")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberNumberToStringType, unknownNumberToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(unknown, number) -> string <: (number, number) -> string")
|
|
{
|
|
CHECK_IS_SUBTYPE(unknownNumberToStringType, numberNumberToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> (string, unknown) <!: (number) -> (string, string)")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberToStringAndUnknownType, numberToTwoStringsType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> (string, string) <: (number) -> (string, unknown)")
|
|
{
|
|
CHECK_IS_SUBTYPE(numberToTwoStringsType, numberToStringAndUnknownType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> (string, string) <!: (number) -> string")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberToTwoStringsType, numberToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> string <!: (number) -> (string, string)")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberToStringType, numberToTwoStringsType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number, ...string) -> string <: (number) -> string")
|
|
{
|
|
CHECK_IS_SUBTYPE(numberAndStringsToStringType, numberToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> string <!: (number, ...string) -> string")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberToStringType, numberAndStringsToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number, ...string?) -> string <: (number, ...string) -> string")
|
|
{
|
|
CHECK_IS_SUBTYPE(numberAndOptionalStringsToStringType, numberAndStringsToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number, ...string) -> string <!: (number, ...string?) -> string")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberAndStringsToStringType, numberAndOptionalStringsToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number, ...string) -> string <: (number, string) -> string")
|
|
{
|
|
CHECK_IS_SUBTYPE(numberAndStringsToStringType, numberAndStringToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number, string) -> string <!: (number, ...string) -> string")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberAndStringToStringType, numberAndStringsToStringType);
|
|
}
|
|
|
|
/*
|
|
* <A>(A) -> A <: <X>(X) -> X
|
|
* A can be bound to X.
|
|
*
|
|
* <A>(A) -> A </: <X>(X) -> number
|
|
* A can be bound to X, but A </: number
|
|
*
|
|
* (number) -> number </: <A>(A) -> A
|
|
* Only generics on the left side can be bound.
|
|
* number </: A
|
|
*
|
|
* <A, B>(A, B) -> boolean <: <X>(X, X) -> boolean
|
|
* It is ok to bind both A and B to X.
|
|
*
|
|
* <A>(A, A) -> boolean </: <X, Y>(X, Y) -> boolean
|
|
* A cannot be bound to both X and Y.
|
|
*/
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>() -> T <: () -> number")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericNothingToTType, nothingToNumberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>(T) -> () <: <U>(U) -> ()")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericTToNothingType, genericUToNothingType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "() -> number <!: <T>() -> T")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(nothingToNumberType, genericNothingToTType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>(T) -> () <: (number) -> ()")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericTToNothingType, numberToNothingType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>(T) -> T <: (number) -> number")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericTToTType, numberToNumberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>(T) -> T <!: (number) -> string")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(genericTToTType, numberToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>(T) -> () <: <U>(U) -> ()")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericTToNothingType, genericUToNothingType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> () <!: <T>(T) -> ()")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberToNothingType, genericTToNothingType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>() -> (T, T) <!: () -> (string, number)")
|
|
{
|
|
TypeId nothingToTwoTs = arena.addType(FunctionType{{genericT}, {}, builtinTypes->emptyTypePack, arena.addTypePack({genericT, genericT})});
|
|
|
|
TypeId nothingToStringAndNumber = fn({}, {builtinTypes->stringType, builtinTypes->numberType});
|
|
|
|
CHECK_IS_NOT_SUBTYPE(nothingToTwoTs, nothingToStringAndNumber);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<A...>(A...) -> A... <: (number) -> number")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericAsToAsType, numberToNumberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> number <!: <A...>(A...) -> A...")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberToNumberType, genericAsToAsType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<A...>(A...) -> A... <: <B...>(B...) -> B...")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericAsToAsType, genericBsToBsType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<B..., C...>(B...) -> C... <: <A...>(A...) -> A...")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericBsToCsType, genericAsToAsType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<A...>(A...) -> A... <!: <B..., C...>(B...) -> C...")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(genericAsToAsType, genericBsToCsType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<A...>(A...) -> number <: (number) -> number")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericAsToNumberType, numberToNumberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> number <!: <A...>(A...) -> number")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numberToNumberType, genericAsToNumberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<A...>(A...) -> number <: (...number) -> number")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericAsToNumberType, numbersToNumberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(...number) -> number <!: <A...>(A...) -> number")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(numbersToNumberType, genericAsToNumberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<A...>() -> A... <: () -> ()")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericNothingToAsType, nothingToNothingType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "() -> () <!: <A...>() -> A...")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(nothingToNothingType, genericNothingToAsType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<A...>(A...) -> A... <: () -> ()")
|
|
{
|
|
CHECK_IS_SUBTYPE(genericAsToAsType, nothingToNothingType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "() -> () <!: <A...>(A...) -> A...")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(nothingToNothingType, genericAsToAsType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{} <: {}")
|
|
{
|
|
CHECK_IS_SUBTYPE(tbl({}), tbl({}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{x: number} <: {}")
|
|
{
|
|
CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{} <!: {x: number}")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(tbl({}), tbl({{"x", builtinTypes->numberType}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{x: number} <!: {x: string}")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->stringType}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{x: number} <!: {x: number?}")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({{"x", builtinTypes->optionalNumberType}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{x: number?} <!: {x: number}")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(tbl({{"x", builtinTypes->optionalNumberType}}), tbl({{"x", builtinTypes->numberType}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{x: <T>(T) -> ()} <: {x: <U>(U) -> ()}")
|
|
{
|
|
CHECK_IS_SUBTYPE(tbl({{"x", genericTToNothingType}}), tbl({{"x", genericUToNothingType}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ x: number } <: { read x: number }")
|
|
{
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({{"x", Property::readonly(builtinTypes->numberType)}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ x: number } <: { write x: number }")
|
|
{
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({{"x", Property::writeonly(builtinTypes->numberType)}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ x: \"hello\" } <: { read x: string }")
|
|
{
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CHECK_IS_SUBTYPE(tbl({{"x", helloType}}), tbl({{"x", Property::readonly(builtinTypes->stringType)}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ x: string } <: { write x: string }")
|
|
{
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->stringType}}), tbl({{"x", Property::writeonly(builtinTypes->stringType)}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }")
|
|
{
|
|
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}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <!: { @metatable { x: boolean } }")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(meta({}), meta({{"x", builtinTypes->booleanType}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable {} } <: {}")
|
|
{
|
|
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}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <!: { x: number }")
|
|
{
|
|
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_SUBTYPE(negate(builtinTypes->anyType), builtinTypes->stringType);
|
|
TEST_IS_SUBTYPE(negate(meet(builtinTypes->neverType, builtinTypes->unknownType)), builtinTypes->stringType);
|
|
TEST_IS_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_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);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: class")
|
|
{
|
|
CHECK_IS_SUBTYPE(join(childClass, anotherChildClass), builtinTypes->classType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: Child | AnotherChild")
|
|
{
|
|
CHECK_IS_SUBTYPE(join(childClass, anotherChildClass), join(childClass, anotherChildClass));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Child | Root <: Root")
|
|
{
|
|
CHECK_IS_SUBTYPE(join(childClass, rootClass), rootClass);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: class")
|
|
{
|
|
CHECK_IS_SUBTYPE(meet(childClass, anotherChildClass), builtinTypes->classType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Child & Root <: class")
|
|
{
|
|
CHECK_IS_SUBTYPE(meet(childClass, rootClass), builtinTypes->classType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~Root <: class")
|
|
{
|
|
CHECK_IS_SUBTYPE(meet(childClass, negate(rootClass)), builtinTypes->classType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: number")
|
|
{
|
|
CHECK_IS_SUBTYPE(meet(childClass, anotherChildClass), builtinTypes->numberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~GrandchildOne <!: number")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(meet(childClass, negate(grandchildOneClass)), builtinTypes->numberType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "semantic_subtyping_disj")
|
|
{
|
|
TypeId subTy = builtinTypes->unknownType;
|
|
TypeId superTy = join(negate(builtinTypes->numberType), negate(builtinTypes->stringType));
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(result.isSubtype);
|
|
}
|
|
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}")
|
|
{
|
|
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
|
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
|
});
|
|
|
|
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) {
|
|
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
|
});
|
|
|
|
CHECK_IS_SUBTYPE(t1, t2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 where t2 = {trim: (t2) -> t2}")
|
|
{
|
|
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
|
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
|
});
|
|
|
|
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) {
|
|
tt->props["trim"] = fn({ty}, {ty});
|
|
});
|
|
|
|
CHECK_IS_NOT_SUBTYPE(t1, t2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} <!: t2 where t2 = {trim: (t2) -> string}")
|
|
{
|
|
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) {
|
|
tt->props["trim"] = fn({ty}, {ty});
|
|
});
|
|
|
|
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) {
|
|
tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
|
|
});
|
|
|
|
CHECK_IS_NOT_SUBTYPE(t1, t2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Vec2 <: { X: number, Y: number }")
|
|
{
|
|
TypeId xy = tbl({
|
|
{"X", builtinTypes->numberType},
|
|
{"Y", builtinTypes->numberType},
|
|
});
|
|
|
|
CHECK_IS_SUBTYPE(vec2Class, xy);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Vec2 <: { X: number }")
|
|
{
|
|
TypeId x = tbl({
|
|
{"X", builtinTypes->numberType},
|
|
});
|
|
|
|
CHECK_IS_SUBTYPE(vec2Class, x);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ X: number, Y: number } <!: Vec2")
|
|
{
|
|
TypeId xy = tbl({
|
|
{"X", builtinTypes->numberType},
|
|
{"Y", builtinTypes->numberType},
|
|
});
|
|
|
|
CHECK_IS_NOT_SUBTYPE(xy, vec2Class);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ X: number } <!: Vec2")
|
|
{
|
|
TypeId x = tbl({
|
|
{"X", builtinTypes->numberType},
|
|
});
|
|
|
|
CHECK_IS_NOT_SUBTYPE(x, vec2Class);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "table & { X: number, Y: number } <!: Vec2")
|
|
{
|
|
TypeId x = tbl({
|
|
{"X", builtinTypes->numberType},
|
|
{"Y", builtinTypes->numberType},
|
|
});
|
|
|
|
CHECK_IS_NOT_SUBTYPE(meet(builtinTypes->tableType, x), vec2Class);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "Vec2 <!: table & { X: number, Y: number }")
|
|
{
|
|
TypeId xy = tbl({
|
|
{"X", builtinTypes->numberType},
|
|
{"Y", builtinTypes->numberType},
|
|
});
|
|
|
|
CHECK_IS_NOT_SUBTYPE(vec2Class, meet(builtinTypes->tableType, xy));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "ReadOnlyVec2 <!: { X: number, Y: number}")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(readOnlyVec2Class, tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->numberType}}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "ReadOnlyVec2 <: { read X: number, read Y: number}")
|
|
{
|
|
CHECK_IS_SUBTYPE(
|
|
readOnlyVec2Class, tbl({{"X", Property::readonly(builtinTypes->numberType)}, {"Y", Property::readonly(builtinTypes->numberType)}}));
|
|
}
|
|
|
|
TEST_IS_SUBTYPE(vec2Class, tbl({{"X", Property::readonly(builtinTypes->numberType)}, {"Y", Property::readonly(builtinTypes->numberType)}}));
|
|
|
|
TEST_IS_NOT_SUBTYPE(tbl({{"P", grandchildOneClass}}), tbl({{"P", Property::rw(rootClass)}}));
|
|
TEST_IS_SUBTYPE(tbl({{"P", grandchildOneClass}}), tbl({{"P", Property::readonly(rootClass)}}));
|
|
TEST_IS_SUBTYPE(tbl({{"P", rootClass}}), tbl({{"P", Property::writeonly(grandchildOneClass)}}));
|
|
|
|
TEST_IS_NOT_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", rootClass}}));
|
|
TEST_IS_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", Property::readonly(rootClass)}}));
|
|
TEST_IS_NOT_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", grandchildOneClass}}));
|
|
TEST_IS_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", Property::writeonly(grandchildOneClass)}}));
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: { lower : (string) -> string }")
|
|
{
|
|
CHECK_IS_SUBTYPE(helloType, tableWithLower);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <!: { insaneThingNoScalarHas : () -> () }")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(helloType, tableWithoutScalarProp);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "string <: { lower : (string) -> string }")
|
|
{
|
|
CHECK_IS_SUBTYPE(builtinTypes->stringType, tableWithLower);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "string <!: { insaneThingNoScalarHas : () -> () }")
|
|
{
|
|
CHECK_IS_NOT_SUBTYPE(builtinTypes->stringType, tableWithoutScalarProp);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "~fun & (string) -> number <: (string) -> number")
|
|
{
|
|
CHECK_IS_SUBTYPE(meet(negate(builtinTypes->functionType), numberToStringType), numberToStringType);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(string) -> number <: ~fun & (string) -> number")
|
|
{
|
|
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);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "number <: ~~number")
|
|
{
|
|
CHECK_IS_SUBTYPE(builtinTypes->numberType, negate(negate(builtinTypes->numberType)));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "~~number <: number")
|
|
{
|
|
CHECK_IS_SUBTYPE(negate(negate(builtinTypes->numberType)), builtinTypes->numberType);
|
|
}
|
|
|
|
// See https://github.com/luau-lang/luau/issues/767
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(...any) -> () <: <T>(T...) -> ()")
|
|
{
|
|
TypeId anysToNothing = arena.addType(FunctionType{builtinTypes->anyTypePack, builtinTypes->emptyTypePack});
|
|
TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack});
|
|
|
|
CHECK_MESSAGE(subtyping.isSubtype(anysToNothing, genericTToAnys).isSubtype, "(...any) -> () <: <T>(T...) -> ()");
|
|
}
|
|
|
|
// See https://github.com/luau-lang/luau/issues/767
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: <T>(T...) -> ()")
|
|
{
|
|
TypeId anysToNothing = arena.addType(FunctionType{arena.addTypePack(VariadicTypePack{builtinTypes->unknownType}), builtinTypes->emptyTypePack});
|
|
TypeId genericTToAnys = arena.addType(FunctionType{genericAs, builtinTypes->emptyTypePack});
|
|
|
|
CHECK_MESSAGE(subtyping.isSubtype(anysToNothing, genericTToAnys).isSubtype, "(...unknown) -> () <: <T>(T...) -> ()");
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
TEST_IS_SUBTYPE(idx(builtinTypes->numberType, builtinTypes->numberType), tbl({}));
|
|
TEST_IS_NOT_SUBTYPE(tbl({}), idx(builtinTypes->numberType, builtinTypes->numberType));
|
|
|
|
TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), idx(builtinTypes->numberType, builtinTypes->numberType));
|
|
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->numberType, builtinTypes->numberType), tbl({{"X", builtinTypes->numberType}}));
|
|
|
|
TEST_IS_NOT_SUBTYPE(
|
|
idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType), idx(builtinTypes->numberType, builtinTypes->numberType));
|
|
TEST_IS_NOT_SUBTYPE(
|
|
idx(builtinTypes->numberType, builtinTypes->numberType), idx(join(builtinTypes->numberType, builtinTypes->stringType), builtinTypes->numberType));
|
|
|
|
TEST_IS_NOT_SUBTYPE(
|
|
idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)), idx(builtinTypes->numberType, builtinTypes->numberType));
|
|
TEST_IS_NOT_SUBTYPE(
|
|
idx(builtinTypes->numberType, builtinTypes->numberType), idx(builtinTypes->numberType, join(builtinTypes->stringType, builtinTypes->numberType)));
|
|
|
|
TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), idx(builtinTypes->stringType, builtinTypes->numberType));
|
|
TEST_IS_SUBTYPE(idx(builtinTypes->stringType, builtinTypes->numberType), tbl({{"X", builtinTypes->numberType}}));
|
|
|
|
TEST_IS_NOT_SUBTYPE(tbl({{"X", opt(builtinTypes->numberType)}}), idx(builtinTypes->stringType, builtinTypes->numberType));
|
|
TEST_IS_NOT_SUBTYPE(idx(builtinTypes->stringType, builtinTypes->numberType), tbl({{"X", opt(builtinTypes->numberType)}}));
|
|
|
|
TEST_IS_SUBTYPE(tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->numberType}}), tbl({{"X", builtinTypes->numberType}}));
|
|
TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->numberType}}));
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "interior_tests_are_cached")
|
|
{
|
|
TypeId tableA = tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->numberType}});
|
|
TypeId tableB = tbl({{"X", builtinTypes->optionalNumberType}, {"Y", builtinTypes->optionalNumberType}});
|
|
|
|
CHECK_IS_NOT_SUBTYPE(tableA, tableB);
|
|
|
|
const SubtypingResult* cachedResult = subtyping.peekCache().find({builtinTypes->numberType, builtinTypes->optionalNumberType});
|
|
REQUIRE(cachedResult);
|
|
|
|
CHECK(cachedResult->isSubtype);
|
|
|
|
cachedResult = subtyping.peekCache().find({tableA, tableB});
|
|
REQUIRE(cachedResult);
|
|
|
|
CHECK(!cachedResult->isSubtype);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "results_that_are_contingent_on_generics_are_not_cached")
|
|
{
|
|
// <T>(T) -> T <: (number) -> number
|
|
CHECK_IS_SUBTYPE(genericTToTType, numberToNumberType);
|
|
|
|
CHECK(subtyping.peekCache().empty());
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "dont_cache_tests_involving_cycles")
|
|
{
|
|
TypeId tableA = arena.addType(BlockedType{});
|
|
TypeId tableA2 = tbl({{"self", tableA}});
|
|
asMutable(tableA)->ty.emplace<BoundType>(tableA2);
|
|
|
|
TypeId tableB = arena.addType(BlockedType{});
|
|
TypeId tableB2 = tbl({{"self", tableB}});
|
|
asMutable(tableB)->ty.emplace<BoundType>(tableB2);
|
|
|
|
CHECK_IS_SUBTYPE(tableA, tableB);
|
|
|
|
CHECK(!subtyping.peekCache().find({tableA, tableB}));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "<T>({ x: T }) -> T <: ({ method: <T>({ x: T }) -> T, x: number }) -> number")
|
|
{
|
|
// <T>({ x: T }) -> T
|
|
TypeId tableToPropType = arena.addType(FunctionType{{genericT}, {}, arena.addTypePack({tbl({{"x", genericT}})}), arena.addTypePack({genericT})});
|
|
|
|
// ({ method: <T>({ x: T }) -> T, x: number }) -> number
|
|
TypeId otherType = fn({tbl({{"method", tableToPropType}, {"x", builtinTypes->numberType}})}, {builtinTypes->numberType});
|
|
|
|
CHECK_IS_SUBTYPE(tableToPropType, otherType);
|
|
}
|
|
|
|
TEST_SUITE_END();
|
|
|
|
TEST_SUITE_BEGIN("Subtyping.Subpaths");
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "table_property")
|
|
{
|
|
TypeId subTy = tbl({{"X", builtinTypes->numberType}});
|
|
TypeId superTy = tbl({{"X", builtinTypes->booleanType}});
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")),
|
|
/* superPath */ Path(TypePath::Property::read("X")),
|
|
/* variance */ SubtypingVariance::Invariant}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "table_indexers")
|
|
{
|
|
TypeId subTy = idx(builtinTypes->numberType, builtinTypes->stringType);
|
|
TypeId superTy = idx(builtinTypes->stringType, builtinTypes->numberType);
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
|
/* subPath */ Path(TypePath::TypeField::IndexLookup),
|
|
/* superPath */ Path(TypePath::TypeField::IndexLookup),
|
|
/* variance */ SubtypingVariance::Invariant,
|
|
},
|
|
SubtypingReasoning{
|
|
/* subPath */ Path(TypePath::TypeField::IndexResult),
|
|
/* superPath */ Path(TypePath::TypeField::IndexResult),
|
|
/* variance */ SubtypingVariance::Invariant,
|
|
}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments")
|
|
{
|
|
TypeId subTy = fn({builtinTypes->numberType}, {});
|
|
TypeId superTy = fn({builtinTypes->stringType}, {});
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
|
/* subPath */ TypePath::PathBuilder().args().index(0).build(),
|
|
/* superPath */ TypePath::PathBuilder().args().index(0).build(),
|
|
/* variance */ SubtypingVariance::Contravariant,
|
|
}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "arity_mismatch")
|
|
{
|
|
TypeId subTy = fn({builtinTypes->numberType}, {});
|
|
TypeId superTy = fn({}, {});
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
|
/* subPath */ TypePath::PathBuilder().args().build(),
|
|
/* superPath */ TypePath::PathBuilder().args().build(),
|
|
/* variance */ SubtypingVariance::Contravariant,
|
|
}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "fn_arguments_tail")
|
|
{
|
|
TypeId subTy = fn({}, VariadicTypePack{builtinTypes->numberType}, {});
|
|
TypeId superTy = fn({}, VariadicTypePack{builtinTypes->stringType}, {});
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
|
/* subPath */ TypePath::PathBuilder().args().tail().variadic().build(),
|
|
/* superPath */ TypePath::PathBuilder().args().tail().variadic().build(),
|
|
/* variance */ SubtypingVariance::Contravariant,
|
|
}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "fn_rets")
|
|
{
|
|
TypeId subTy = fn({}, {builtinTypes->numberType});
|
|
TypeId superTy = fn({}, {builtinTypes->stringType});
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
|
/* subPath */ TypePath::PathBuilder().rets().index(0).build(),
|
|
/* superPath */ TypePath::PathBuilder().rets().index(0).build(),
|
|
}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "fn_rets_tail")
|
|
{
|
|
TypeId subTy = fn({}, {}, VariadicTypePack{builtinTypes->numberType});
|
|
TypeId superTy = fn({}, {}, VariadicTypePack{builtinTypes->stringType});
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
|
/* subPath */ TypePath::PathBuilder().rets().tail().variadic().build(),
|
|
/* superPath */ TypePath::PathBuilder().rets().tail().variadic().build(),
|
|
}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "nested_table_properties")
|
|
{
|
|
TypeId subTy = tbl({{"X", tbl({{"Y", tbl({{"Z", builtinTypes->numberType}})}})}});
|
|
TypeId superTy = tbl({{"X", tbl({{"Y", tbl({{"Z", builtinTypes->stringType}})}})}});
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
|
/* subPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(),
|
|
/* superPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(),
|
|
/* variance */ SubtypingVariance::Invariant,
|
|
}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "string_table_mt")
|
|
{
|
|
TypeId subTy = builtinTypes->stringType;
|
|
TypeId superTy = tbl({{"X", builtinTypes->numberType}});
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
// This check is weird. Because we don't have built-in types, we don't have
|
|
// the string metatable. That means subtyping will see that the entire
|
|
// metatable is empty, and abort there, without looking at the metatable
|
|
// properties (because there aren't any).
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
|
/* subPath */ TypePath::PathBuilder().mt().readProp("__index").build(),
|
|
/* superPath */ TypePath::kEmpty,
|
|
}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "negation")
|
|
{
|
|
TypeId subTy = builtinTypes->numberType;
|
|
TypeId superTy = negate(builtinTypes->numberType);
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
|
/* subPath */ TypePath::kEmpty,
|
|
/* superPath */ Path(TypePath::TypeField::Negated),
|
|
}});
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings")
|
|
{
|
|
TypeId subTy = tbl({{"X", builtinTypes->stringType}, {"Y", builtinTypes->numberType}});
|
|
TypeId superTy = tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->stringType}});
|
|
|
|
SubtypingResult result = isSubtype(subTy, superTy);
|
|
CHECK(!result.isSubtype);
|
|
CHECK(result.reasoning ==
|
|
std::vector{
|
|
SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), /* superPath */ Path(TypePath::Property::read("X")),
|
|
/* variance */ SubtypingVariance::Invariant},
|
|
SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("Y")), /* superPath */ Path(TypePath::Property::read("Y")),
|
|
/* variance */ SubtypingVariance::Invariant},
|
|
});
|
|
}
|
|
|
|
TEST_SUITE_END();
|