2021-10-30 04:25:12 +08:00
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
2021-11-05 10:34:35 +08:00
|
|
|
#include "Luau/Scope.h"
|
2021-10-30 04:25:12 +08:00
|
|
|
#include "Luau/TypeInfer.h"
|
|
|
|
#include "Luau/TypeVar.h"
|
|
|
|
|
|
|
|
#include "Fixture.h"
|
|
|
|
|
|
|
|
#include "doctest.h"
|
|
|
|
|
|
|
|
using namespace Luau;
|
|
|
|
|
|
|
|
struct TryUnifyFixture : Fixture
|
|
|
|
{
|
|
|
|
TypeArena arena;
|
|
|
|
ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}};
|
|
|
|
InternalErrorReporter iceHandler;
|
2021-11-05 23:47:21 +08:00
|
|
|
UnifierSharedState unifierState{&iceHandler};
|
2022-10-07 08:23:29 +08:00
|
|
|
Normalizer normalizer{&arena, singletonTypes, NotNull{&unifierState}};
|
|
|
|
Unifier state{NotNull{&normalizer}, Mode::Strict, NotNull{globalScope.get()}, Location{}, Variance::Covariant};
|
2021-10-30 04:25:12 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
TEST_SUITE_BEGIN("TryUnifyTests");
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "primitives_unify")
|
|
|
|
{
|
|
|
|
TypeVar numberOne{TypeVariant{PrimitiveTypeVar{PrimitiveTypeVar::Number}}};
|
|
|
|
TypeVar numberTwo = numberOne;
|
|
|
|
|
2022-01-07 09:46:53 +08:00
|
|
|
state.tryUnify(&numberTwo, &numberOne);
|
2021-10-30 04:25:12 +08:00
|
|
|
|
|
|
|
CHECK(state.errors.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified")
|
|
|
|
{
|
|
|
|
TypeVar functionOne{
|
|
|
|
TypeVariant{FunctionTypeVar(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({typeChecker.numberType}))}};
|
|
|
|
|
|
|
|
TypeVar functionTwo{TypeVariant{
|
|
|
|
FunctionTypeVar(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({arena.freshType(globalScope->level)}))}};
|
|
|
|
|
2022-01-07 09:46:53 +08:00
|
|
|
state.tryUnify(&functionTwo, &functionOne);
|
2021-10-30 04:25:12 +08:00
|
|
|
CHECK(state.errors.empty());
|
|
|
|
|
2022-03-12 00:55:02 +08:00
|
|
|
state.log.commit();
|
2022-01-07 09:46:53 +08:00
|
|
|
|
2021-10-30 04:25:12 +08:00
|
|
|
CHECK_EQ(functionOne, functionTwo);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_functions_are_preserved")
|
|
|
|
{
|
|
|
|
TypePackVar argPackOne{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}};
|
|
|
|
TypeVar functionOne{
|
|
|
|
TypeVariant{FunctionTypeVar(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({typeChecker.numberType}))}};
|
|
|
|
|
|
|
|
TypeVar functionOneSaved = functionOne;
|
|
|
|
|
|
|
|
TypePackVar argPackTwo{TypePack{{arena.freshType(globalScope->level)}, std::nullopt}};
|
|
|
|
TypeVar functionTwo{
|
|
|
|
TypeVariant{FunctionTypeVar(arena.addTypePack({arena.freshType(globalScope->level)}), arena.addTypePack({typeChecker.stringType}))}};
|
|
|
|
|
|
|
|
TypeVar functionTwoSaved = functionTwo;
|
|
|
|
|
2022-01-07 09:46:53 +08:00
|
|
|
state.tryUnify(&functionTwo, &functionOne);
|
2021-10-30 04:25:12 +08:00
|
|
|
CHECK(!state.errors.empty());
|
|
|
|
|
|
|
|
CHECK_EQ(functionOne, functionOneSaved);
|
|
|
|
CHECK_EQ(functionTwo, functionTwoSaved);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "tables_can_be_unified")
|
|
|
|
{
|
|
|
|
TypeVar tableOne{TypeVariant{
|
|
|
|
TableTypeVar{{{"foo", {arena.freshType(globalScope->level)}}}, std::nullopt, globalScope->level, TableState::Unsealed},
|
|
|
|
}};
|
|
|
|
|
|
|
|
TypeVar tableTwo{TypeVariant{
|
|
|
|
TableTypeVar{{{"foo", {arena.freshType(globalScope->level)}}}, std::nullopt, globalScope->level, TableState::Unsealed},
|
|
|
|
}};
|
|
|
|
|
|
|
|
CHECK_NE(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
|
|
|
|
|
2022-01-07 09:46:53 +08:00
|
|
|
state.tryUnify(&tableTwo, &tableOne);
|
2021-10-30 04:25:12 +08:00
|
|
|
|
|
|
|
CHECK(state.errors.empty());
|
|
|
|
|
2022-03-12 00:55:02 +08:00
|
|
|
state.log.commit();
|
2022-01-07 09:46:53 +08:00
|
|
|
|
2021-10-30 04:25:12 +08:00
|
|
|
CHECK_EQ(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved")
|
|
|
|
{
|
|
|
|
TypeVar tableOne{TypeVariant{
|
|
|
|
TableTypeVar{{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {typeChecker.numberType}}}, std::nullopt, globalScope->level,
|
|
|
|
TableState::Unsealed},
|
|
|
|
}};
|
|
|
|
|
|
|
|
TypeVar tableTwo{TypeVariant{
|
|
|
|
TableTypeVar{{{"foo", {arena.freshType(globalScope->level)}}, {"bar", {typeChecker.stringType}}}, std::nullopt, globalScope->level,
|
|
|
|
TableState::Unsealed},
|
|
|
|
}};
|
|
|
|
|
|
|
|
CHECK_NE(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
|
|
|
|
|
2022-01-07 09:46:53 +08:00
|
|
|
state.tryUnify(&tableTwo, &tableOne);
|
2021-10-30 04:25:12 +08:00
|
|
|
|
|
|
|
CHECK_EQ(1, state.errors.size());
|
|
|
|
|
|
|
|
CHECK_NE(*getMutable<TableTypeVar>(&tableOne)->props["foo"].type, *getMutable<TableTypeVar>(&tableTwo)->props["foo"].type);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "members_of_failed_typepack_unification_are_unified_with_errorType")
|
|
|
|
{
|
|
|
|
CheckResult result = check(R"(
|
|
|
|
function f(arg: number) end
|
|
|
|
local a
|
|
|
|
local b
|
|
|
|
f(a, b)
|
|
|
|
)");
|
|
|
|
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
|
2021-11-20 00:10:07 +08:00
|
|
|
CHECK_EQ("a", toString(requireType("a")));
|
2022-11-05 01:33:22 +08:00
|
|
|
CHECK_EQ("*error-type*", toString(requireType("b")));
|
2021-11-20 00:10:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "result_of_failed_typepack_unification_is_constrained")
|
|
|
|
{
|
|
|
|
CheckResult result = check(R"(
|
|
|
|
function f(arg: number) return arg end
|
|
|
|
local a
|
|
|
|
local b
|
|
|
|
local c = f(a, b)
|
|
|
|
)");
|
|
|
|
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
2021-10-30 04:25:12 +08:00
|
|
|
|
2021-11-20 00:10:07 +08:00
|
|
|
CHECK_EQ("a", toString(requireType("a")));
|
2022-11-05 01:33:22 +08:00
|
|
|
CHECK_EQ("*error-type*", toString(requireType("b")));
|
2021-11-20 00:10:07 +08:00
|
|
|
CHECK_EQ("number", toString(requireType("c")));
|
2021-10-30 04:25:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "typepack_unification_should_trim_free_tails")
|
|
|
|
{
|
|
|
|
CheckResult result = check(R"(
|
|
|
|
--!strict
|
|
|
|
local function f(v: number)
|
|
|
|
if v % 2 == 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return function()
|
|
|
|
return (f(1))
|
|
|
|
end
|
|
|
|
)");
|
|
|
|
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
2022-03-05 00:36:33 +08:00
|
|
|
CHECK_EQ("(number) -> boolean", toString(requireType("f")));
|
2021-10-30 04:25:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_type_pack_unification")
|
|
|
|
{
|
|
|
|
TypePackVar testPack{TypePack{{typeChecker.numberType, typeChecker.stringType}, std::nullopt}};
|
|
|
|
TypePackVar variadicPack{VariadicTypePack{typeChecker.numberType}};
|
|
|
|
|
2022-01-07 09:46:53 +08:00
|
|
|
state.tryUnify(&testPack, &variadicPack);
|
2021-10-30 04:25:12 +08:00
|
|
|
CHECK(!state.errors.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "variadic_tails_respect_progress")
|
|
|
|
{
|
|
|
|
TypePackVar variadicPack{VariadicTypePack{typeChecker.booleanType}};
|
|
|
|
TypePackVar a{TypePack{{typeChecker.numberType, typeChecker.stringType, typeChecker.booleanType, typeChecker.booleanType}}};
|
|
|
|
TypePackVar b{TypePack{{typeChecker.numberType, typeChecker.stringType}, &variadicPack}};
|
|
|
|
|
2022-01-07 09:46:53 +08:00
|
|
|
state.tryUnify(&b, &a);
|
2021-10-30 04:25:12 +08:00
|
|
|
CHECK(state.errors.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "variadics_should_use_reversed_properly")
|
|
|
|
{
|
|
|
|
CheckResult result = check(R"(
|
|
|
|
--!strict
|
|
|
|
local function f<T>(...: T): ...T
|
|
|
|
return ...
|
|
|
|
end
|
|
|
|
|
|
|
|
local x: string = f(1)
|
|
|
|
)");
|
|
|
|
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
|
|
|
REQUIRE(tm);
|
|
|
|
CHECK_EQ(toString(tm->givenType), "number");
|
|
|
|
CHECK_EQ(toString(tm->wantedType), "string");
|
|
|
|
}
|
|
|
|
|
2022-05-14 03:36:37 +08:00
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "cli_41095_concat_log_in_sealed_table_unification")
|
2021-10-30 04:25:12 +08:00
|
|
|
{
|
|
|
|
CheckResult result = check(R"(
|
|
|
|
--!strict
|
|
|
|
table.insert()
|
|
|
|
)");
|
|
|
|
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
|
|
CHECK_EQ(toString(result.errors[0]), "No overload for function accepts 0 arguments.");
|
|
|
|
CHECK_EQ(toString(result.errors[1]), "Available overloads: ({a}, a) -> (); and ({a}, number, a) -> ()");
|
|
|
|
}
|
|
|
|
|
2022-01-07 09:46:53 +08:00
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly")
|
|
|
|
{
|
|
|
|
TypePackId threeNumbers = arena.addTypePack(TypePack{{typeChecker.numberType, typeChecker.numberType, typeChecker.numberType}, std::nullopt});
|
|
|
|
TypePackId numberAndFreeTail = arena.addTypePack(TypePack{{typeChecker.numberType}, arena.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})});
|
|
|
|
|
|
|
|
ErrorVec unifyErrors = state.canUnify(numberAndFreeTail, threeNumbers);
|
|
|
|
CHECK(unifyErrors.size() == 0);
|
|
|
|
}
|
|
|
|
|
2022-02-05 00:45:57 +08:00
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag")
|
|
|
|
{
|
|
|
|
TypeVar redirect{FreeTypeVar{TypeLevel{}}};
|
|
|
|
TypeVar table{TableTypeVar{}};
|
|
|
|
TypeVar metatable{MetatableTypeVar{&redirect, &table}};
|
|
|
|
redirect = BoundTypeVar{&metatable}; // Now we have a metatable that is recursive on the table type
|
|
|
|
TypeVar variant{UnionTypeVar{{&metatable, typeChecker.numberType}}};
|
|
|
|
|
|
|
|
state.tryUnify(&metatable, &variant);
|
|
|
|
}
|
|
|
|
|
2022-02-12 03:02:09 +08:00
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "cli_50320_follow_in_any_unification")
|
|
|
|
{
|
|
|
|
TypePackVar free{FreeTypePack{TypeLevel{}}};
|
|
|
|
TypePackVar target{TypePack{}};
|
|
|
|
|
|
|
|
TypeVar func{FunctionTypeVar{&free, &free}};
|
|
|
|
|
|
|
|
state.tryUnify(&free, &target);
|
|
|
|
// Shouldn't assert or error.
|
|
|
|
state.tryUnify(&func, typeChecker.anyType);
|
|
|
|
}
|
|
|
|
|
2022-04-08 05:29:01 +08:00
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_type_owner")
|
|
|
|
{
|
|
|
|
TypeId a = arena.addType(TypeVar{FreeTypeVar{TypeLevel{}}});
|
|
|
|
TypeId b = typeChecker.numberType;
|
|
|
|
|
|
|
|
state.tryUnify(a, b);
|
|
|
|
state.log.commit();
|
|
|
|
|
|
|
|
CHECK_EQ(a->owningArena, &arena);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "txnlog_preserves_pack_owner")
|
|
|
|
{
|
|
|
|
TypePackId a = arena.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}});
|
|
|
|
TypePackId b = typeChecker.anyTypePack;
|
|
|
|
|
|
|
|
state.tryUnify(a, b);
|
|
|
|
state.log.commit();
|
|
|
|
|
|
|
|
CHECK_EQ(a->owningArena, &arena);
|
|
|
|
}
|
|
|
|
|
2022-09-30 06:23:10 +08:00
|
|
|
TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table")
|
|
|
|
{
|
|
|
|
ScopedFastFlag sff("DebugLuauDeferredConstraintResolution", true);
|
|
|
|
|
|
|
|
TableTypeVar::Props freeProps{
|
|
|
|
{"foo", {typeChecker.numberType}},
|
|
|
|
};
|
|
|
|
|
|
|
|
TypeId free = arena.addType(TableTypeVar{freeProps, std::nullopt, TypeLevel{}, TableState::Free});
|
|
|
|
|
|
|
|
TableTypeVar::Props indexProps{
|
|
|
|
{"foo", {typeChecker.stringType}},
|
|
|
|
};
|
|
|
|
|
|
|
|
TypeId index = arena.addType(TableTypeVar{indexProps, std::nullopt, TypeLevel{}, TableState::Sealed});
|
|
|
|
|
|
|
|
TableTypeVar::Props mtProps{
|
|
|
|
{"__index", {index}},
|
|
|
|
};
|
|
|
|
|
|
|
|
TypeId mt = arena.addType(TableTypeVar{mtProps, std::nullopt, TypeLevel{}, TableState::Sealed});
|
|
|
|
|
|
|
|
TypeId target = arena.addType(TableTypeVar{TableState::Unsealed, TypeLevel{}});
|
|
|
|
TypeId metatable = arena.addType(MetatableTypeVar{target, mt});
|
|
|
|
|
|
|
|
state.tryUnify(metatable, free);
|
|
|
|
state.log.commit();
|
|
|
|
|
|
|
|
REQUIRE_EQ(state.errors.size(), 1);
|
|
|
|
|
|
|
|
std::string expected = "Type '{ @metatable {| __index: {| foo: string |} |}, { } }' could not be converted into '{- foo: number -}'\n"
|
2022-10-15 03:48:41 +08:00
|
|
|
"caused by:\n"
|
|
|
|
" Type 'number' could not be converted into 'string'";
|
2022-09-30 06:23:10 +08:00
|
|
|
CHECK_EQ(toString(state.errors[0]), expected);
|
|
|
|
}
|
|
|
|
|
2021-10-30 04:25:12 +08:00
|
|
|
TEST_SUITE_END();
|