mirror of
https://github.com/luau-lang/luau.git
synced 2025-08-26 11:27:08 +01:00
# What's Changed? We've been hard at work fixing bugs in the new type solver and getting it ready to go! ## Native Codegen * Specialized Luau Codegen instruction for fetching an import. As a reminder, an import is an expression like `global.thing` and covers stuff like libraries without fastcalls `coroutine.resume`) and atomic extern libraries. ## New Type Solver * Fix an issue that prevented eager generalization from working properly with OO styled code. * Avoid copying uninitialized memory in Luau attribute parsing * Improve type inference of unsealed tables. * This fixes https://github.com/luau-lang/luau/issues/1838 * and https://github.com/luau-lang/luau/issues/1859 * Infer potential singleton string keys in autocomplete when the expected index type is a union type. * Avoid creating cyclic types when reducing types of the form `t1 where t1 = refine<T, t1, Y>` * The type cloner now does the same thing for the new and old solvers. * Properly infer polarity (aka variance) for divergent table properties. (ie tables whose read type and write type are not the same) * Crash fixes. --------- Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com> Co-authored-by: Menarul Alam <malam@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
714 lines
25 KiB
C++
714 lines
25 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
#include "Fixture.h"
|
|
|
|
#include "Luau/EqSatSimplification.h"
|
|
#include "Luau/Type.h"
|
|
|
|
using namespace Luau;
|
|
|
|
struct ESFixture : Fixture
|
|
{
|
|
ScopedFastFlag newSolverOnly{FFlag::LuauSolverV2, true};
|
|
|
|
TypeArena arena_;
|
|
const NotNull<TypeArena> arena{&arena_};
|
|
|
|
SimplifierPtr simplifier;
|
|
|
|
TypeId parentClass;
|
|
TypeId childClass;
|
|
TypeId anotherChild;
|
|
TypeId unrelatedClass;
|
|
|
|
TypeId genericT = arena_.addType(GenericType{"T"});
|
|
TypeId genericU = arena_.addType(GenericType{"U"});
|
|
|
|
TypeId numberToString =
|
|
arena_.addType(FunctionType{arena_.addTypePack({getBuiltins()->numberType}), arena_.addTypePack({getBuiltins()->stringType})});
|
|
|
|
TypeId stringToNumber =
|
|
arena_.addType(FunctionType{arena_.addTypePack({getBuiltins()->stringType}), arena_.addTypePack({getBuiltins()->numberType})});
|
|
|
|
ESFixture()
|
|
: simplifier(newSimplifier(arena, getBuiltins()))
|
|
{
|
|
createSomeExternTypes(getFrontend());
|
|
|
|
ScopePtr moduleScope = getFrontend().globals.globalScope;
|
|
|
|
parentClass = moduleScope->linearSearchForBinding("Parent")->typeId;
|
|
childClass = moduleScope->linearSearchForBinding("Child")->typeId;
|
|
anotherChild = moduleScope->linearSearchForBinding("AnotherChild")->typeId;
|
|
unrelatedClass = moduleScope->linearSearchForBinding("Unrelated")->typeId;
|
|
}
|
|
|
|
std::optional<std::string> simplifyStr(TypeId ty)
|
|
{
|
|
auto res = eqSatSimplify(NotNull{simplifier.get()}, ty);
|
|
LUAU_ASSERT(res);
|
|
return toString(res->result);
|
|
}
|
|
|
|
TypeId tbl(TableType::Props props)
|
|
{
|
|
return arena->addType(TableType{std::move(props), std::nullopt, TypeLevel{}, TableState::Sealed});
|
|
}
|
|
};
|
|
|
|
TEST_SUITE_BEGIN("EqSatSimplification");
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "primitive")
|
|
{
|
|
CHECK("number" == simplifyStr(getBuiltins()->numberType));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "number | number")
|
|
{
|
|
TypeId ty = arena->addType(UnionType{{getBuiltins()->numberType, getBuiltins()->numberType}});
|
|
|
|
CHECK("number" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "number | string")
|
|
{
|
|
CHECK("number | string" == simplifyStr(arena->addType(UnionType{{getBuiltins()->numberType, getBuiltins()->stringType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "t1 where t1 = number | t1")
|
|
{
|
|
TypeId ty = arena->freshType(getBuiltins(), nullptr);
|
|
asMutable(ty)->ty.emplace<UnionType>(std::vector<TypeId>{getBuiltins()->numberType, ty});
|
|
|
|
CHECK("number" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "number | string | number")
|
|
{
|
|
TypeId ty = arena->addType(UnionType{{getBuiltins()->numberType, getBuiltins()->stringType, getBuiltins()->numberType}});
|
|
|
|
CHECK("number | string" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string | (number | string) | number")
|
|
{
|
|
TypeId u1 = arena->addType(UnionType{{getBuiltins()->numberType, getBuiltins()->stringType}});
|
|
TypeId u2 = arena->addType(UnionType{{getBuiltins()->stringType, u1, getBuiltins()->numberType}});
|
|
|
|
CHECK("number | string" == simplifyStr(u2));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string | any")
|
|
{
|
|
CHECK("any" == simplifyStr(arena->addType(UnionType{{getBuiltins()->stringType, getBuiltins()->anyType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "any | string")
|
|
{
|
|
CHECK("any" == simplifyStr(arena->addType(UnionType{{getBuiltins()->anyType, getBuiltins()->stringType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "any | never")
|
|
{
|
|
CHECK("any" == simplifyStr(arena->addType(UnionType{{getBuiltins()->anyType, getBuiltins()->neverType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string | unknown")
|
|
{
|
|
CHECK("unknown" == simplifyStr(arena->addType(UnionType{{getBuiltins()->stringType, getBuiltins()->unknownType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "unknown | string")
|
|
{
|
|
CHECK("unknown" == simplifyStr(arena->addType(UnionType{{getBuiltins()->unknownType, getBuiltins()->stringType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "unknown | never")
|
|
{
|
|
CHECK("unknown" == simplifyStr(arena->addType(UnionType{{getBuiltins()->unknownType, getBuiltins()->neverType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string | never")
|
|
{
|
|
CHECK("string" == simplifyStr(arena->addType(UnionType{{getBuiltins()->stringType, getBuiltins()->neverType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string | never | number")
|
|
{
|
|
CHECK("number | string" == simplifyStr(arena->addType(UnionType{{getBuiltins()->stringType, getBuiltins()->neverType, getBuiltins()->numberType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string & string")
|
|
{
|
|
CHECK("string" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->stringType, getBuiltins()->stringType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string & number")
|
|
{
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->stringType, getBuiltins()->numberType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string & unknown")
|
|
{
|
|
CHECK("string" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->stringType, getBuiltins()->unknownType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "never & string")
|
|
{
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->neverType, getBuiltins()->stringType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string & (unknown | never)")
|
|
{
|
|
CHECK(
|
|
"string" == simplifyStr(arena->addType(
|
|
IntersectionType{{getBuiltins()->stringType, arena->addType(UnionType{{getBuiltins()->unknownType, getBuiltins()->neverType}})}}
|
|
))
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "true | false")
|
|
{
|
|
CHECK("boolean" == simplifyStr(arena->addType(UnionType{{getBuiltins()->trueType, getBuiltins()->falseType}})));
|
|
}
|
|
|
|
/*
|
|
* Intuitively, if we have a type like
|
|
*
|
|
* x where x = A & B & (C | D | x)
|
|
*
|
|
* We know that x is certainly not larger than A & B.
|
|
* We also know that the union (C | D | x) can be rewritten `(C | D | (A & B & (C | D | x)))
|
|
* This tells us that the union part is not smaller than A & B.
|
|
* We can therefore discard the union entirely and simplify this type to A & B
|
|
*/
|
|
TEST_CASE_FIXTURE(ESFixture, "t1 where t1 = string & (number | t1)")
|
|
{
|
|
TypeId intersectionTy = arena->addType(BlockedType{});
|
|
TypeId unionTy = arena->addType(UnionType{{getBuiltins()->numberType, intersectionTy}});
|
|
|
|
asMutable(intersectionTy)->ty.emplace<IntersectionType>(std::vector<TypeId>{getBuiltins()->stringType, unionTy});
|
|
|
|
CHECK("string" == simplifyStr(intersectionTy));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "t1 where t1 = string & (unknown | t1)")
|
|
{
|
|
TypeId intersectionTy = arena->addType(BlockedType{});
|
|
TypeId unionTy = arena->addType(UnionType{{getBuiltins()->unknownType, intersectionTy}});
|
|
|
|
asMutable(intersectionTy)->ty.emplace<IntersectionType>(std::vector<TypeId>{getBuiltins()->stringType, unionTy});
|
|
|
|
CHECK("string" == simplifyStr(intersectionTy));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "error | unknown")
|
|
{
|
|
CHECK("any" == simplifyStr(arena->addType(UnionType{{getBuiltins()->errorType, getBuiltins()->unknownType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "\"hello\" | string")
|
|
{
|
|
CHECK("string" == simplifyStr(arena->addType(UnionType{{arena->addType(SingletonType{StringSingleton{"hello"}}), getBuiltins()->stringType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "\"hello\" | \"world\" | \"hello\"")
|
|
{
|
|
CHECK(
|
|
"\"hello\" | \"world\"" == simplifyStr(arena->addType(UnionType{{
|
|
arena->addType(SingletonType{StringSingleton{"hello"}}),
|
|
arena->addType(SingletonType{StringSingleton{"world"}}),
|
|
arena->addType(SingletonType{StringSingleton{"hello"}}),
|
|
}}))
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | function | table | class | buffer")
|
|
{
|
|
CHECK(
|
|
"unknown" == simplifyStr(arena->addType(UnionType{{
|
|
getBuiltins()->nilType,
|
|
getBuiltins()->booleanType,
|
|
getBuiltins()->numberType,
|
|
getBuiltins()->stringType,
|
|
getBuiltins()->threadType,
|
|
getBuiltins()->functionType,
|
|
getBuiltins()->tableType,
|
|
getBuiltins()->externType,
|
|
getBuiltins()->bufferType,
|
|
}}))
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "Parent & number")
|
|
{
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{parentClass, getBuiltins()->numberType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "Child & Parent")
|
|
{
|
|
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{childClass, parentClass}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "Child & Unrelated")
|
|
{
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{childClass, unrelatedClass}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "Child | Parent")
|
|
{
|
|
CHECK("Parent" == simplifyStr(arena->addType(UnionType{{childClass, parentClass}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "class | Child")
|
|
{
|
|
CHECK("class" == simplifyStr(arena->addType(UnionType{{getBuiltins()->externType, childClass}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "Parent | class | Child")
|
|
{
|
|
CHECK("class" == simplifyStr(arena->addType(UnionType{{parentClass, getBuiltins()->externType, childClass}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "Parent | Unrelated")
|
|
{
|
|
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{parentClass, unrelatedClass}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "never | Parent | Unrelated")
|
|
{
|
|
CHECK("Parent | Unrelated" == simplifyStr(arena->addType(UnionType{{getBuiltins()->neverType, parentClass, unrelatedClass}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "never | Parent | (number & string) | Unrelated")
|
|
{
|
|
CHECK(
|
|
"Parent | Unrelated" == simplifyStr(arena->addType(UnionType{
|
|
{getBuiltins()->neverType,
|
|
parentClass,
|
|
arena->addType(IntersectionType{{getBuiltins()->numberType, getBuiltins()->stringType}}),
|
|
unrelatedClass}
|
|
}))
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "T & U")
|
|
{
|
|
CHECK("T & U" == simplifyStr(arena->addType(IntersectionType{{genericT, genericU}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "boolean & true")
|
|
{
|
|
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->booleanType, getBuiltins()->trueType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "boolean & (true | number | string | thread | function | table | class | buffer)")
|
|
{
|
|
TypeId truthy = arena->addType(UnionType{{
|
|
getBuiltins()->trueType,
|
|
getBuiltins()->numberType,
|
|
getBuiltins()->stringType,
|
|
getBuiltins()->threadType,
|
|
getBuiltins()->functionType,
|
|
getBuiltins()->tableType,
|
|
getBuiltins()->externType,
|
|
getBuiltins()->bufferType,
|
|
}});
|
|
|
|
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->booleanType, truthy}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "boolean & ~(false?)")
|
|
{
|
|
CHECK("true" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->booleanType, getBuiltins()->truthyType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "false & ~(false?)")
|
|
{
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->falseType, getBuiltins()->truthyType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number) -> string & (number) -> string")
|
|
{
|
|
CHECK("(number) -> string" == simplifyStr(arena->addType(IntersectionType{{numberToString, numberToString}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number) -> string | (number) -> string")
|
|
{
|
|
CHECK("(number) -> string" == simplifyStr(arena->addType(UnionType{{numberToString, numberToString}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number) -> string & function")
|
|
{
|
|
CHECK("(number) -> string" == simplifyStr(arena->addType(IntersectionType{{numberToString, getBuiltins()->functionType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number) -> string & boolean")
|
|
{
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{numberToString, getBuiltins()->booleanType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number) -> string & string")
|
|
{
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{numberToString, getBuiltins()->stringType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number) -> string & ~function")
|
|
{
|
|
TypeId notFunction = arena->addType(NegationType{getBuiltins()->functionType});
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{numberToString, notFunction}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number) -> string | function")
|
|
{
|
|
CHECK("function" == simplifyStr(arena->addType(UnionType{{numberToString, getBuiltins()->functionType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number) -> string & (string) -> number")
|
|
{
|
|
CHECK("((number) -> string) & ((string) -> number)" == simplifyStr(arena->addType(IntersectionType{{numberToString, stringToNumber}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number) -> string | (string) -> number")
|
|
{
|
|
CHECK("((number) -> string) | ((string) -> number)" == simplifyStr(arena->addType(UnionType{{numberToString, stringToNumber}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "add<number, number>")
|
|
{
|
|
CHECK(
|
|
"number" ==
|
|
simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {getBuiltins()->numberType, getBuiltins()->numberType}}))
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "union<number, number>")
|
|
{
|
|
CHECK(
|
|
"number" ==
|
|
simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {getBuiltins()->numberType, getBuiltins()->numberType}}))
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "never & ~string")
|
|
{
|
|
CHECK(
|
|
"never" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->neverType, arena->addType(NegationType{getBuiltins()->stringType})}}))
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "blocked & never")
|
|
{
|
|
const TypeId blocked = arena->addType(BlockedType{});
|
|
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{blocked, getBuiltins()->neverType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "blocked & ~number & function")
|
|
{
|
|
const TypeId blocked = arena->addType(BlockedType{});
|
|
const TypeId notNumber = arena->addType(NegationType{getBuiltins()->numberType});
|
|
|
|
const TypeId ty = arena->addType(IntersectionType{{blocked, notNumber, getBuiltins()->functionType}});
|
|
|
|
std::string expected = toString(blocked) + " & function";
|
|
|
|
CHECK(expected == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number | boolean | string | nil | table) & (false | nil)")
|
|
{
|
|
const TypeId t1 = arena->addType(
|
|
UnionType{{getBuiltins()->numberType, getBuiltins()->booleanType, getBuiltins()->stringType, getBuiltins()->nilType, getBuiltins()->tableType}}
|
|
);
|
|
|
|
CHECK("false?" == simplifyStr(arena->addType(IntersectionType{{t1, getBuiltins()->falsyType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(number | boolean | nil) & (false | nil)")
|
|
{
|
|
const TypeId t1 = arena->addType(UnionType{{getBuiltins()->numberType, getBuiltins()->booleanType, getBuiltins()->nilType}});
|
|
|
|
CHECK("false?" == simplifyStr(arena->addType(IntersectionType{{t1, getBuiltins()->falsyType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(boolean | nil) & (false | nil)")
|
|
{
|
|
const TypeId t1 = arena->addType(UnionType{{getBuiltins()->booleanType, getBuiltins()->nilType}});
|
|
|
|
CHECK("false?" == simplifyStr(arena->addType(IntersectionType{{t1, getBuiltins()->falsyType}})));
|
|
}
|
|
|
|
// (('a & false) | ('a & nil)) | number
|
|
|
|
// Child & ~Parent
|
|
// ~Parent & Child
|
|
// ~Child & Parent
|
|
// Parent & ~Child
|
|
// ~Child & ~Parent
|
|
// ~Parent & ~Child
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "free & string & number")
|
|
{
|
|
Scope scope{getBuiltins()->anyTypePack};
|
|
const TypeId freeTy = arena->freshType(getBuiltins(), &scope);
|
|
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{freeTy, getBuiltins()->numberType, getBuiltins()->stringType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(blocked & number) | (blocked & number)")
|
|
{
|
|
const TypeId blocked = arena->addType(BlockedType{});
|
|
const TypeId u = arena->addType(IntersectionType{{blocked, getBuiltins()->numberType}});
|
|
const TypeId ty = arena->addType(UnionType{{u, u}});
|
|
|
|
const std::string blockedStr = toString(blocked);
|
|
|
|
CHECK(blockedStr + " & number" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "{} & unknown")
|
|
{
|
|
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), getBuiltins()->unknownType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "{} & table")
|
|
{
|
|
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), getBuiltins()->tableType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "{} & ~(false?)")
|
|
{
|
|
CHECK("{ }" == simplifyStr(arena->addType(IntersectionType{{tbl({}), getBuiltins()->truthyType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "{x: number?} & {x: number}")
|
|
{
|
|
const TypeId hasOptionalX = tbl({{"x", getBuiltins()->optionalNumberType}});
|
|
const TypeId hasX = tbl({{"x", getBuiltins()->numberType}});
|
|
|
|
const TypeId ty = arena->addType(IntersectionType{{hasOptionalX, hasX}});
|
|
auto res = eqSatSimplify(NotNull{simplifier.get()}, ty);
|
|
|
|
CHECK("{ x: number }" == toString(res->result));
|
|
|
|
// Also assert that we don't allocate a fresh TableType in this case.
|
|
CHECK(follow(res->result) == hasX);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "{x: number?} & {x: ~(false?)}")
|
|
{
|
|
const TypeId hasOptionalX = tbl({{"x", getBuiltins()->optionalNumberType}});
|
|
const TypeId hasX = tbl({{"x", getBuiltins()->truthyType}});
|
|
|
|
const TypeId ty = arena->addType(IntersectionType{{hasOptionalX, hasX}});
|
|
auto res = eqSatSimplify(NotNull{simplifier.get()}, ty);
|
|
|
|
CHECK("{ x: number }" == toString(res->result));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(({ x: number? }?) & { x: ~(false?) }")
|
|
{
|
|
// {x: number?}?
|
|
const TypeId xWithOptionalNumber = arena->addType(UnionType{{tbl({{"x", getBuiltins()->optionalNumberType}}), getBuiltins()->nilType}});
|
|
|
|
// {x: ~(false?)}
|
|
const TypeId xWithTruthy = tbl({{"x", getBuiltins()->truthyType}});
|
|
|
|
const TypeId ty = arena->addType(IntersectionType{{xWithOptionalNumber, xWithTruthy}});
|
|
|
|
CHECK("{ x: number }" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "never | (({ x: number? }?) & { x: ~(false?) })")
|
|
{
|
|
// {x: number?}?
|
|
const TypeId xWithOptionalNumber = arena->addType(UnionType{{tbl({{"x", getBuiltins()->optionalNumberType}}), getBuiltins()->nilType}});
|
|
|
|
// {x: ~(false?)}
|
|
const TypeId xWithTruthy = tbl({{"x", getBuiltins()->truthyType}});
|
|
|
|
// ({x: number?}?) & {x: ~(false?)}
|
|
const TypeId intersectionTy = arena->addType(IntersectionType{{xWithOptionalNumber, xWithTruthy}});
|
|
|
|
const TypeId ty = arena->addType(UnionType{{getBuiltins()->neverType, intersectionTy}});
|
|
|
|
CHECK("{ x: number }" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "({ x: number? }?) & { x: ~(false?) } & ~(false?)")
|
|
{
|
|
// {x: number?}?
|
|
const TypeId xWithOptionalNumber = arena->addType(UnionType{{tbl({{"x", getBuiltins()->optionalNumberType}}), getBuiltins()->nilType}});
|
|
|
|
// {x: ~(false?)}
|
|
const TypeId xWithTruthy = tbl({{"x", getBuiltins()->truthyType}});
|
|
|
|
// ({x: number?}?) & {x: ~(false?)} & ~(false?)
|
|
const TypeId intersectionTy = arena->addType(IntersectionType{{xWithOptionalNumber, xWithTruthy, getBuiltins()->truthyType}});
|
|
|
|
CHECK("{ x: number }" == simplifyStr(intersectionTy));
|
|
}
|
|
|
|
#if 0
|
|
// TODO
|
|
TEST_CASE_FIXTURE(ESFixture, "(({ x: number? }?) & { x: ~(false?) } & ~(false?)) | number")
|
|
{
|
|
// ({ x: number? }?) & { x: ~(false?) } & ~(false?)
|
|
const TypeId xWithOptionalNumber = tbl({{"x", getBuiltins()->optionalNumberType}});
|
|
const TypeId xWithTruthy = tbl({{"x", getBuiltins()->truthyType}});
|
|
const TypeId intersectionTy = arena->addType(IntersectionType{{xWithOptionalNumber, xWithTruthy, getBuiltins()->truthyType}});
|
|
const TypeId ty = arena->addType(UnionType{{intersectionTy, getBuiltins()->numberType}});
|
|
|
|
CHECK("{ x: number } | number" == simplifyStr(ty));
|
|
}
|
|
#endif
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "number & no-refine")
|
|
{
|
|
CHECK("number" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->numberType, getBuiltins()->noRefineType}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "{ x: number } & ~boolean")
|
|
{
|
|
const TypeId tblTy = tbl(TableType::Props{{"x", getBuiltins()->numberType}});
|
|
|
|
const TypeId ty = arena->addType(IntersectionType{{tblTy, arena->addType(NegationType{getBuiltins()->booleanType})}});
|
|
|
|
CHECK("{ x: number }" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(nil & string)?")
|
|
{
|
|
const TypeId nilAndString = arena->addType(IntersectionType{{getBuiltins()->nilType, getBuiltins()->stringType}});
|
|
const TypeId ty = arena->addType(UnionType{{nilAndString, getBuiltins()->nilType}});
|
|
|
|
CHECK("nil" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string & \"hi\"")
|
|
{
|
|
const TypeId hi = arena->addType(SingletonType{StringSingleton{"hi"}});
|
|
|
|
CHECK("\"hi\"" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->stringType, hi}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string & (\"hi\" | \"bye\")")
|
|
{
|
|
const TypeId hi = arena->addType(SingletonType{StringSingleton{"hi"}});
|
|
const TypeId bye = arena->addType(SingletonType{StringSingleton{"bye"}});
|
|
|
|
CHECK("\"bye\" | \"hi\"" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->stringType, arena->addType(UnionType{{hi, bye}})}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(\"err\" | \"ok\") & ~\"ok\"")
|
|
{
|
|
TypeId err = arena->addType(SingletonType{StringSingleton{"err"}});
|
|
TypeId ok1 = arena->addType(SingletonType{StringSingleton{"ok"}});
|
|
TypeId ok2 = arena->addType(SingletonType{StringSingleton{"ok"}});
|
|
|
|
TypeId ty = arena->addType(IntersectionType{{arena->addType(UnionType{{err, ok1}}), arena->addType(NegationType{ok2})}});
|
|
|
|
CHECK("\"err\"" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & ~Child")
|
|
{
|
|
const TypeId ty =
|
|
arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, unrelatedClass}}), arena->addType(NegationType{childClass})}});
|
|
|
|
CHECK("Unrelated" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string & ~Child")
|
|
{
|
|
CHECK("string" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->stringType, arena->addType(NegationType{childClass})}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(Child | Unrelated) & Child")
|
|
{
|
|
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, unrelatedClass}}), childClass}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "(Child | AnotherChild) & ~Child")
|
|
{
|
|
CHECK("Child" == simplifyStr(arena->addType(IntersectionType{{arena->addType(UnionType{{childClass, anotherChild}}), childClass}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "{ tag: \"Part\", x: never }")
|
|
{
|
|
const TypeId ty = tbl({{"tag", arena->addType(SingletonType{StringSingleton{"Part"}})}, {"x", getBuiltins()->neverType}});
|
|
|
|
CHECK("never" == simplifyStr(ty));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "{ tag: \"Part\", x: number? } & { x: string }")
|
|
{
|
|
const TypeId leftTable = tbl({{"tag", arena->addType(SingletonType{StringSingleton{"Part"}})}, {"x", getBuiltins()->optionalNumberType}});
|
|
const TypeId rightTable = tbl({{"x", getBuiltins()->stringType}});
|
|
|
|
CHECK("never" == simplifyStr(arena->addType(IntersectionType{{leftTable, rightTable}})));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "Child & add<Child | AnotherChild | string, Parent>")
|
|
{
|
|
const TypeId u = arena->addType(UnionType{{childClass, anotherChild, getBuiltins()->stringType}});
|
|
const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {u, parentClass}, {}});
|
|
|
|
const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}});
|
|
|
|
CHECK("Child & add<AnotherChild | Child | string, Parent>" == simplifyStr(intersection));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "Child & intersect<Child | AnotherChild | string, Parent>")
|
|
{
|
|
const TypeId u = arena->addType(UnionType{{childClass, anotherChild, getBuiltins()->stringType}});
|
|
const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().intersectFunc, {u, parentClass}, {}});
|
|
|
|
const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}});
|
|
|
|
CHECK("Child" == simplifyStr(intersection));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "lt<number, _> == boolean")
|
|
{
|
|
std::vector<std::pair<TypeId, TypeId>> cases{
|
|
{getBuiltins()->numberType, arena->addType(BlockedType{})},
|
|
{getBuiltins()->stringType, arena->addType(BlockedType{})},
|
|
{arena->addType(BlockedType{}), getBuiltins()->numberType},
|
|
{arena->addType(BlockedType{}), getBuiltins()->stringType},
|
|
};
|
|
|
|
for (const auto& [lhs, rhs] : cases)
|
|
{
|
|
const TypeId tfun = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().ltFunc, {lhs, rhs}});
|
|
CHECK("boolean" == simplifyStr(tfun));
|
|
}
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "unknown & ~string")
|
|
{
|
|
CHECK_EQ(
|
|
"~string", simplifyStr(arena->addType(IntersectionType{{getBuiltins()->unknownType, arena->addType(NegationType{getBuiltins()->stringType})}}))
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ESFixture, "string & ~\"foo\"")
|
|
{
|
|
CHECK_EQ(
|
|
"string & ~\"foo\"",
|
|
simplifyStr(arena->addType(
|
|
IntersectionType{{getBuiltins()->stringType, arena->addType(NegationType{arena->addType(SingletonType{StringSingleton{"foo"}})})}}
|
|
))
|
|
);
|
|
}
|
|
|
|
// {someKey: ~any}
|
|
//
|
|
// Maybe something we could do here is to try to reduce the key, get the
|
|
// class->node mapping, and skip the extraction process if the class corresponds
|
|
// to TNever.
|
|
|
|
// t1 where t1 = add<union<number, t1>, number>
|
|
|
|
TEST_SUITE_END();
|