luau/tests/TypeInfer.intersectionTypes...

905 lines
27 KiB
C++
Raw Normal View History

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h"
#include "Fixture.h"
#include "doctest.h"
using namespace Luau;
2022-04-14 19:57:43 -04:00
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
TEST_SUITE_BEGIN("IntersectionTypes");
TEST_CASE_FIXTURE(Fixture, "select_correct_union_fn")
{
CheckResult result = check(R"(
type A = (number) -> (string)
type B = (string) -> (number)
local f:A & B
local b = f(10) -- b is a string
local c = f("a") -- c is a number
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(requireType("b"), typeChecker.stringType);
CHECK_EQ(requireType("c"), typeChecker.numberType);
}
TEST_CASE_FIXTURE(Fixture, "table_combines")
{
CheckResult result = check(R"(
type A={a:number}
type B={b:string}
local c:A & B = {a=10, b="s"}
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "table_combines_missing")
{
CheckResult result = check(R"(
type A={a:number}
type B={b:string}
local c:A & B = {a=10}
)");
REQUIRE(result.errors.size() == 1);
}
TEST_CASE_FIXTURE(Fixture, "impossible_type")
{
CheckResult result = check(R"(
local c:number&string = 10
)");
REQUIRE(result.errors.size() == 1);
}
TEST_CASE_FIXTURE(Fixture, "table_extra_ok")
{
CheckResult result = check(R"(
type A={a:number}
type B={b:string}
local c:A & B
local d:A = c
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "fx_intersection_as_argument")
{
CheckResult result = check(R"(
type A = (number) -> (string)
type B = (string) -> (number)
type C = (A) -> (number)
local f:A & B
local g:C
local b = g(f)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "fx_union_as_argument_fails")
{
CheckResult result = check(R"(
type A = (number) -> (string)
type B = (string) -> (number)
type C = (A) -> (number)
local f:A | B
local g:C
local b = g(f)
)");
REQUIRE(!result.errors.empty());
}
TEST_CASE_FIXTURE(Fixture, "argument_is_intersection")
{
CheckResult result = check(R"(
type A = (number | boolean) -> number
local f: A
f(5)
f(true)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "should_still_pick_an_overload_whose_arguments_are_unions")
{
CheckResult result = check(R"(
type A = (number | boolean) -> number
type B = (string | nil) -> string
local f: A & B
local a1, a2 = f(1), f(true)
local b1, b2 = f("foo"), f(nil)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*requireType("a1"), *typeChecker.numberType);
CHECK_EQ(*requireType("a2"), *typeChecker.numberType);
CHECK_EQ(*requireType("b1"), *typeChecker.stringType);
CHECK_EQ(*requireType("b2"), *typeChecker.stringType);
}
TEST_CASE_FIXTURE(Fixture, "propagates_name")
{
const std::string code = R"(
type A={a:number}
type B={b:string}
local c:A&B
local b = c
)";
const std::string expected = R"(
type A={a:number}
type B={b:string}
local c:A&B
local b:A&B=c
)";
CHECK_EQ(expected, decorateWithTypes(code));
}
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guaranteed_to_exist")
{
CheckResult result = check(R"(
type A = {x: {y: number}}
type B = {x: {y: number}}
local t: A & B
local r = t.x
)");
LUAU_REQUIRE_NO_ERRORS(result);
const IntersectionTypeVar* r = get<IntersectionTypeVar>(requireType("r"));
REQUIRE(r);
TableTypeVar* a = getMutable<TableTypeVar>(r->parts[0]);
REQUIRE(a);
CHECK_EQ(typeChecker.numberType, a->props["y"].type);
TableTypeVar* b = getMutable<TableTypeVar>(r->parts[1]);
REQUIRE(b);
CHECK_EQ(typeChecker.numberType, b->props["y"].type);
}
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth")
{
CheckResult result = check(R"(
type A = {x: {y: {z: {thing: string}}}}
type B = {x: {y: {z: {thing: string}}}}
local t: A & B
local r = t.x.y.z.thing
)");
LUAU_REQUIRE_NO_ERRORS(result);
2022-02-24 18:53:37 -05:00
CHECK_EQ("string & string", toString(requireType("r")));
}
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types")
{
CheckResult result = check(R"(
type A = {x: number}
type B = {x: string}
local t: A & B
local r = t.x
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("number & string", toString(requireType("r"))); // TODO(amccord): This should be an error.
}
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_part_missing_the_property")
{
CheckResult result = check(R"(
type A = {x: number}
type B = {}
local t: A & B
local r = t.x
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("number", toString(requireType("r")));
}
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_property_of_type_any")
{
CheckResult result = check(R"(
2022-02-24 18:53:37 -05:00
type A = {y: number}
type B = {x: any}
local t: A & B
local r = t.x
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*typeChecker.anyType, *requireType("r"));
}
TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_all_parts_missing_the_property")
{
CheckResult result = check(R"(
type A = {}
type B = {}
local function f(t: A & B)
local x = t.x
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
UnknownProperty* up = get<UnknownProperty>(result.errors[0]);
REQUIRE_MESSAGE(up, result.errors[0].data);
CHECK_EQ(up->key, "x");
}
TEST_CASE_FIXTURE(Fixture, "table_intersection_write")
{
CheckResult result = check(R"(
type X = { x: number }
type XY = X & { y: number }
local a : XY = { x = 1, y = 2 }
a.x = 10
)");
LUAU_REQUIRE_NO_ERRORS(result);
result = check(R"(
type X = {}
type XY = X & { x: number, y: number }
local a : XY = { x = 1, y = 2 }
a.x = 10
)");
LUAU_REQUIRE_NO_ERRORS(result);
result = check(R"(
type X = { x: number }
type Y = { y: number }
type XY = X & Y
local a : XY = { x = 1, y = 2 }
a.x = 10
)");
LUAU_REQUIRE_NO_ERRORS(result);
result = check(R"(
type A = { x: {y: number} }
type B = { x: {y: number} }
local t : A & B = { x = { y = 1 } }
t.x = { y = 4 }
t.x.y = 40
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed")
{
CheckResult result = check(R"(
type X = { x: number }
type Y = { y: number }
type XY = X & Y
local a : XY = { x = 1, y = 2 }
a.z = 10
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
2022-04-14 19:57:43 -04:00
if (FFlag::LuauLowerBoundsCalculation)
CHECK_EQ(toString(result.errors[0]), "Cannot add property 'z' to table '{| x: number, y: number |}'");
else
CHECK_EQ(toString(result.errors[0]), "Cannot add property 'z' to table 'X & Y'");
}
TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
{
CheckResult result = check(R"(
2022-04-14 19:57:43 -04:00
type X = { x: (number) -> number }
type Y = { y: (string) -> string }
2022-04-14 19:57:43 -04:00
type XY = X & Y
2022-04-14 19:57:43 -04:00
local xy : XY = {
x = function(a: number) return -a end,
y = function(a: string) return a .. "b" end
}
function xy.z(a:number) return a * 10 end
function xy:y(a:number) return a * 10 end
function xy:w(a:number) return a * 10 end
)");
2022-04-07 17:29:01 -04:00
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string'
caused by:
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)");
2022-04-14 19:57:43 -04:00
if (FFlag::LuauLowerBoundsCalculation)
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table '{| x: (number) -> number, y: (string) -> string |}'");
else
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
2022-04-07 17:29:01 -04:00
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
2022-04-14 19:57:43 -04:00
if (FFlag::LuauLowerBoundsCalculation)
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table '{| x: (number) -> number, y: (string) -> string |}'");
else
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'X & Y'");
2022-04-07 17:29:01 -04:00
}
TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
{
// After normalization, previous 'table_intersection_write_sealed_indirect' is identical to this one
CheckResult result = check(R"(
type XY = { x: (number) -> number, y: (string) -> string }
local xy : XY = {
x = function(a: number) return -a end,
y = function(a: string) return a .. "b" end
}
function xy.z(a:number) return a * 10 end
function xy:y(a:number) return a * 10 end
function xy:w(a:number) return a * 10 end
)");
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK_EQ(toString(result.errors[0]), R"(Type '(string, number) -> string' could not be converted into '(string) -> string'
caused by:
Argument count mismatch. Function expects 2 arguments, but only 1 is specified)");
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'");
CHECK_EQ(toString(result.errors[2]), "Type 'number' could not be converted into 'string'");
CHECK_EQ(toString(result.errors[3]), "Cannot add property 'w' to table 'XY'");
}
2022-05-13 15:36:37 -04:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_intersection_setmetatable")
{
CheckResult result = check(R"(
local t: {} & {}
setmetatable(t, {})
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_part")
{
2022-04-14 19:57:43 -04:00
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", false}};
CheckResult result = check(R"(
type X = { x: number }
type Y = { y: number }
type Z = { z: number }
type XYZ = X & Y & Z
local a: XYZ = 3
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'X & Y & Z'
caused by:
Not all intersection parts are compatible. Type 'number' could not be converted into 'X')");
}
TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all")
{
2022-04-14 19:57:43 -04:00
ScopedFastFlag flags[] = {{"LuauLowerBoundsCalculation", false}};
CheckResult result = check(R"(
type X = { x: number }
type Y = { y: number }
type Z = { z: number }
type XYZ = X & Y & Z
local a: XYZ
local b: number = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), R"(Type 'X & Y & Z' could not be converted into 'number'; none of the intersection parts are compatible)");
}
2022-03-17 20:46:04 -04:00
TEST_CASE_FIXTURE(Fixture, "overload_is_not_a_function")
{
check(R"(
--!nonstrict
function _(...):((typeof(not _))&(typeof(not _)))&((typeof(not _))&(typeof(not _)))
_(...)(setfenv,_,not _,"")[_] = nil
end
do end
_(...)(...,setfenv,_):_G()
)");
}
TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_flattenintersection")
{
CheckResult result = check(R"(
local l0,l0
repeat
type t0 = ((any)|((any)&((any)|((any)&((any)|(any))))))&(t0)
function _(l0):(t0)&(t0)
2022-04-14 19:57:43 -04:00
while nil do
end
2022-03-17 20:46:04 -04:00
end
until _(_)(_)._
)");
LUAU_REQUIRE_ERRORS(result);
2022-03-17 20:46:04 -04:00
}
TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
{
CheckResult result = check(R"(
local x : (boolean & false)
local y : false = x -- OK
local z : true = x -- Not OK
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type 'boolean & false' could not be converted into 'true'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
{
CheckResult result = check(R"(
local x : false & (boolean & false)
local y : false = x -- OK
local z : true = x -- Not OK
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
// TODO: odd stringification of `false & (boolean & false)`.)
CHECK_EQ(toString(result.errors[0]), "Type 'boolean & false & false' could not be converted into 'true'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : ((number?) -> number?) & ((string?) -> string?)
local y : (nil) -> nil = x -- OK
local z : (number) -> number = x -- Not OK
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> number?) & ((string?) -> string?)' could not be converted into '(number) -> number'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : ((number) -> number) & ((string) -> string)
local y : ((number | string) -> (number | string)) = x -- OK
local z : ((number | boolean) -> (number | boolean)) = x -- Not OK
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number) & ((string) -> string)' could not be converted into '(boolean | number) -> boolean | number'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? }
local y : { p : number?, q : nil, r : number? } = x -- OK
local z : { p : nil } = x -- Not OK
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' could not be converted into '{| p: nil |}'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
{
CheckResult result = check(R"(
local x : { p : number?, q : any } & { p : unknown, q : string? }
local y : { p : number?, q : string? } = x -- OK
local z : { p : string?, q : number? } = x -- Not OK
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, q: number? |}'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : { p : number?, q : never } & { p : never, q : string? }
local y : { p : never, q : never } = x -- OK
local z : never = x -- OK
)");
// TODO: this should not produce type errors, since never <: { p : never }
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '{| p: never, q: string? |} & {| p: number?, q: never |}' could not be converted into 'never'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number }))
local y : (nil) -> { p : number, q : number, r : number} = x -- OK
local z : (number?) -> { p : number, q : number, r : number} = x -- Not OK
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' could not be converted into '(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a>()
local x : ((number?) -> (a | number)) & ((string?) -> (a | string))
local y : (nil) -> a = x -- OK
local z : (number?) -> a = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> a | number) & ((string?) -> a | string)' could not be converted into '(number?) -> a'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a,b,c>()
local x : ((a?) -> (a | b)) & ((c?) -> (b | c))
local y : (nil) -> ((a & c) | b) = x -- OK
local z : (a?) -> ((a & c) | b) = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((a?) -> a | b) & ((c?) -> b | c)' could not be converted into '(a?) -> (a & c) | b'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))
local y : ((nil, a...) -> (nil, b...)) = x -- OK
local z : ((nil, b...) -> (nil, a...)) = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))' could not be converted into '(nil, b...) -> (nil, a...)'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number) -> number) & ((nil) -> unknown)
local y : (number?) -> unknown = x -- OK
local z : (number?) -> number? = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> unknown) & ((number) -> number)' could not be converted into '(number?) -> number?'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number) -> number?) & ((unknown) -> string?)
local y : (number) -> nil = x -- OK
local z : (number?) -> nil = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((number) -> number?) & ((unknown) -> string?)' could not be converted into '(number?) -> nil'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number) -> number) & ((nil) -> never)
local y : (number?) -> number = x -- OK
local z : (number?) -> never = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((nil) -> never) & ((number) -> number)' could not be converted into '(number?) -> never'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((number) -> number?) & ((never) -> string?)
local y : (never) -> nil = x -- OK
local z : (number?) -> nil = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((never) -> string?) & ((number) -> number?)' could not be converted into '(number?) -> nil'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics")
{
CheckResult result = check(R"(
local x : ((string?) -> (string | number)) & ((number?) -> ...number)
local y : ((nil) -> (number, number?)) = x -- OK
local z : ((string | number) -> (number, number?)) = x -- Not OK
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((number?) -> (...number)) & ((string?) -> number | string)' could not be converted into '(number | string) -> (number, number?)'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
{
CheckResult result = check(R"(
function f<a...,b...>()
local x : (() -> a...) & (() -> b...)
local y : (() -> b...) & (() -> a...) = x -- OK
local z : () -> () = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '(() -> (a...)) & (() -> (b...))' could not be converted into '() -> ()'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
{
CheckResult result = check(R"(
function f<a...,b...>()
local x : ((a...) -> ()) & ((b...) -> ())
local y : ((b...) -> ()) & ((a...) -> ()) = x -- OK
local z : () -> () = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((b...) -> ())' could not be converted into '() -> ()'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
{
CheckResult result = check(R"(
function f<a...>()
local x : (() -> a...) & (() -> (number?,a...))
local y : (() -> (number?,a...)) & (() -> a...) = x -- OK
local z : () -> (number) = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '(() -> (a...)) & (() -> (number?, a...))' could not be converted into '() -> number'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
{
CheckResult result = check(R"(
function f<a...>()
local x : ((a...) -> ()) & ((number,a...) -> number)
local y : ((number,a...) -> number) & ((a...) -> ()) = x -- OK
local z : (number?) -> () = x -- Not OK
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type '((a...) -> ()) & ((number, a...) -> number)' could not be converted into '(number?) -> ()'; none of the intersection parts are compatible");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local a : string? = nil
local b : number? = nil
local x = setmetatable({}, { p = 5, q = a });
local y = setmetatable({}, { q = b, r = "hi" });
local z = setmetatable({}, { p = 5, q = nil, r = "hi" });
type X = typeof(x)
type Y = typeof(y)
type Z = typeof(z)
local xy : X&Y = z;
local yx : Y&X = z;
z = xy;
z = yx;
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x = setmetatable({ a = 5 }, { p = 5 });
local y = setmetatable({ b = "hi" }, { p = 5, q = "hi" });
local z = setmetatable({ a = 5, b = "hi" }, { p = 5, q = "hi" });
type X = typeof(x)
type Y = typeof(y)
type Z = typeof(z)
local xy : X&Y = z;
local yx : Y&X = z;
z = xy;
z = yx;
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x = setmetatable({ a = 5 }, { p = 5 });
local y = setmetatable({ b = "hi" }, { q = "hi" });
local z = setmetatable({ a = 5, b = "hi" }, { p = 5, q = "hi" });
type X = typeof(x)
type Y = typeof(y)
type Z = typeof(z)
local xy : X&Y = z;
z = xy;
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with table")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
local x = setmetatable({ a = 5 }, { p = 5 });
local z = setmetatable({ a = 5, b = "hi" }, { p = 5 });
type X = typeof(x)
type Y = { b : string }
type Z = typeof(z)
-- TODO: once we have shape types, we should be able to initialize these with z
local xy : X&Y;
local yx : Y&X;
z = xy;
z = yx;
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "CLI-44817")
{
ScopedFastFlag sffs[] {
{"LuauSubtypeNormalizer", true},
{"LuauTypeNormalization2", true},
};
CheckResult result = check(R"(
type X = {x: number}
type Y = {y: number}
type Z = {z: number}
type XY = {x: number, y: number}
type XYZ = {x:number, y: number, z: number}
local xy: XY = {x = 0, y = 0}
local xyz: XYZ = {x = 0, y = 0, z = 0}
local xNy: X&Y = xy
local xNyNz: X&Y&Z = xyz
local t1: XY = xNy -- Type 'X & Y' could not be converted into 'XY'
local t2: XY = xNyNz -- Type 'X & Y & Z' could not be converted into 'XY'
local t3: XYZ = xNyNz -- Type 'X & Y & Z' could not be converted into 'XYZ'
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();