Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion Analysis/src/TypeFunctionRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypeFunctionTableIndexerIsReadOnly)
LUAU_FASTFLAGVARIABLE(LuauUdtfCreateSingletonFixErrorMessage)
LUAU_FASTFLAGVARIABLE(LuauUdtfTypeUseTaggedMetatable)
LUAU_FASTFLAGVARIABLE(LuauUdtfTypeToStringMetamethod)
LUAU_FASTFLAGVARIABLE(LuauUdtfTypeSetReadIndexerMethod)

namespace Luau
{
Expand Down Expand Up @@ -1111,7 +1112,35 @@ static int setTableIndexer(lua_State* L)
// Sets the read indexer of the table
static int setTableReadIndexer(lua_State* L)
{
luaL_error(L, "type.setreadindexer: luau does not yet support separate read/write types for indexers.");
if (!(FFlag::LuauUdtfTypeSetReadIndexerMethod && FFlag::LuauTypeFunctionTableIndexerIsReadOnly))
luaL_error(L, "type.setreadindexer: this function is not enabled yet");

int argumentCount = lua_gettop(L);
if (argumentCount == 2 && get<TypeFunctionNeverType>(getTypeUserData(L, 2)))
luaL_error(
L,
"type.setreadindexer: expected 3 arguments, but got 2; the key was types.never, did you mean to do :setreadindexer(types.never, "
"types.never)?"
);
if (argumentCount != 3)
luaL_error(L, "type.setreadindexer: expected 3 arguments, but got %d", argumentCount);

TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tftt = getMutable<TypeFunctionTableType>(self);
if (!tftt)
luaL_error(L, "type.setreadindexer: expected self to be either a table, but got %s instead", getTag(L, self).c_str());

if (FFlag::LuauTypeFunctionSupportsFrozen && self->frozen)
luaL_error(L, "type.setreadindexer: cannot be called to mutate a frozen type, use `types.copy` to make a copy");

TypeFunctionTypeId key = getTypeUserData(L, 2);
if (tftt->indexer && !tftt->indexer->isReadOnly)
luaL_error(L, "types.setreadindexer: the table has an existing read+write indexer, which luau does not support setreadindexer for yet");

TypeFunctionTypeId value = getTypeUserData(L, 3);

tftt->indexer = TypeFunctionTableIndexer{key, value, true};
return 0;
}

// Luau: `self:setwriteindexer(key: type, value: type)`
Expand Down
84 changes: 84 additions & 0 deletions tests/TypeFunction.user.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauReadOnlyIndexers)
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAG(LuauUdtfCreateSingletonFixErrorMessage)
LUAU_FASTFLAG(LuauUdtfTypeToStringMetamethod)
LUAU_FASTFLAG(LuauUdtfTypeSetReadIndexerMethod)

TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");

Expand Down Expand Up @@ -3480,4 +3481,87 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "types_singleton_error_message")
);
}

TEST_CASE_FIXTURE(BuiltinsFixture, "type_setreadindexer_works_for_newtable")
{
DOES_NOT_PASS_OLD_SOLVER_GUARD()
ScopedFastFlag sffs[] = {
{FFlag::LuauUdtfTypeSetReadIndexerMethod, true},
{FFlag::LuauTypeFunctionTableIndexerIsReadOnly, true},
{FFlag::LuauReadOnlyIndexers, true},
};

CheckResult results = check(R"(
type function meow(index: type, value: type)
local new = types.newtable()
new:setreadindexer(index, value)
return new
end

local a: meow<number, string>
local b: meow<string, number>
)");

LUAU_REQUIRE_NO_ERRORS(results);

CHECK_EQ(toString(requireType("a")), "{read string}");
CHECK_EQ(toString(requireType("b")), "{ read [string]: number }");
}

TEST_CASE_FIXTURE(BuiltinsFixture, "type_setreadindexer_works_when_existing_readonly_indexer")
{
DOES_NOT_PASS_OLD_SOLVER_GUARD()
ScopedFastFlag sffs[] = {
{FFlag::LuauUdtfTypeSetReadIndexerMethod, true},
{FFlag::LuauTypeFunctionTableIndexerIsReadOnly, true},
{FFlag::LuauReadOnlyIndexers, true},
};

CheckResult results = check(R"(
type function kya(tbl: type)
tbl:setreadindexer(types.string, types.boolean)
return tbl
end

local a: kya<{ read [number]: number }>
local b: kya<{ read [string]: number }>
)");

LUAU_REQUIRE_NO_ERRORS(results);

CHECK_EQ(toString(requireType("a")), "{ read [string]: boolean }");
CHECK_EQ(toString(requireType("b")), "{ read [string]: boolean }");
}

TEST_CASE_FIXTURE(BuiltinsFixture, "type_setreadindexer_errors_when_existing_readwrite_indexer")
{
DOES_NOT_PASS_OLD_SOLVER_GUARD()
ScopedFastFlag sffs[] = {
{FFlag::LuauUdtfTypeSetReadIndexerMethod, true},
{FFlag::LuauTypeFunctionTableIndexerIsReadOnly, true},
{FFlag::LuauReadOnlyIndexers, true},
};

CheckResult results = check(R"(
type function nyanya(tbl: type)
tbl:setreadindexer(types.string, types.boolean)
return tbl
end

local a: nyanya<{ [number]: number }>
local b: nyanya<{ [string]: number }>
)");

LUAU_REQUIRE_ERROR_COUNT(2, results);
CHECK_EQ(
toString(results.errors[0]),
"'nyanya' type function errored at runtime: [string \"nyanya\"]:3: types.setreadindexer: the table has an existing read+write indexer, which "
"luau does not support setreadindexer for yet"
);
CHECK_EQ(
toString(results.errors[1]),
"'nyanya' type function errored at runtime: [string \"nyanya\"]:3: types.setreadindexer: the table has an existing read+write indexer, which "
"luau does not support setreadindexer for yet"
);
}

TEST_SUITE_END();
Loading