From cef06727705af7c005b9681b0a39ebfa4d092a73 Mon Sep 17 00:00:00 2001 From: Matheus <175355178+matlneus@users.noreply.github.com> Date: Sun, 31 Aug 2025 01:22:35 -0300 Subject: [PATCH 01/12] Initial commit --- Analysis/include/Luau/TypeFunctionRuntime.h | 2 + Analysis/src/TypeFunctionRuntime.cpp | 76 ++++++++++++++++++--- Analysis/src/TypeFunctionRuntimeBuilder.cpp | 12 +++- 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index 6b198c723..05e03d003 100644 --- a/Analysis/include/Luau/TypeFunctionRuntime.h +++ b/Analysis/include/Luau/TypeFunctionRuntime.h @@ -157,6 +157,8 @@ struct TypeFunctionFunctionType TypeFunctionTypePackId argTypes; TypeFunctionTypePackId retTypes; + + std::vector> argNames; }; template diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 2dfd21d54..c055b1d24 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -1092,7 +1092,33 @@ static void pushTypePack(lua_State* L, TypeFunctionTypePackId tp) } } -// Luau: `types.newfunction(parameters: {head: {type}?, tail: type?}, returns: {head: {type}?, tail: type?}, generics: {type}?) -> type` +void setArgNames(lua_State* L, int namesIdx, size_t argLength, std::vector>& argNames) +{ + namesIdx = lua_absindex(L, namesIdx); + + argNames.resize(argLength); + + for (size_t i = 1; i <= argLength; ++i) { + lua_pushinteger(L, i); + lua_gettable(L, namesIdx); + + std::optional argName; + + if (!lua_isnil(L, -1)) + { + TypeFunctionTypeId ty = getTypeUserData(L, -1); + + if (auto tfst = get(ty)) + if (auto tfsst = get(tfst); tfsst && isIdentifier(tfsst->value)) + argName = tfsst->value; + } + + argNames[i - 1] = argName; + lua_pop(L, 1); + } +} + +// Luau: `types.newfunction(parameters: {head: {type}?, names: { [number]: type? }?, tail: type?}, returns: {head: {type}?, tail: type?}, generics: {type}?) -> type` // Returns the type instance representing a function static int createFunction(lua_State* L) { @@ -1101,6 +1127,7 @@ static int createFunction(lua_State* L) luaL_error(L, "types.newfunction: expected 0-3 arguments, but got %d", argumentCount); TypeFunctionTypePackId argTypes = nullptr; + std::vector> argNames; if (lua_istable(L, 1)) { @@ -1109,7 +1136,14 @@ static int createFunction(lua_State* L) argTypes = getTypePack(L, -2, -1); - lua_pop(L, 2); + lua_getfield(L, 1, "names"); + + if (lua_istable(L, -1)) + setArgNames(L, -1, /* argLength */ lua_objlen(L, -3), argNames); + else + luaL_typeerrorL(L, -1, "table"); + + lua_pop(L, 3); } else if (!lua_isnoneornil(L, 1)) { @@ -1142,18 +1176,18 @@ static int createFunction(lua_State* L) auto [genericTypes, genericPacks] = getGenerics(L, 3, "types.newfunction"); - allocTypeUserData(L, TypeFunctionFunctionType{std::move(genericTypes), std::move(genericPacks), argTypes, retTypes}); + allocTypeUserData(L, TypeFunctionFunctionType{std::move(genericTypes), std::move(genericPacks), argTypes, retTypes, argNames}); return 1; } -// Luau: `self:setparameters(head: {type}?, tail: type?)` +// Luau: `self:setparameters(head: {type}?, tail: type?, names: { [number]: type? }?)` // Sets the parameters of the function static int setFunctionParameters(lua_State* L) { int argumentCount = lua_gettop(L); - if (argumentCount > 3 || argumentCount < 1) - luaL_error(L, "type.setparameters: expected 1-3, but got %d", argumentCount); + if (argumentCount > 4 || argumentCount < 1) + luaL_error(L, "type.setparameters: expected 1-4, but got %d", argumentCount); TypeFunctionTypeId self = getTypeUserData(L, 1); auto tfft = getMutable(self); @@ -1162,10 +1196,15 @@ static int setFunctionParameters(lua_State* L) tfft->argTypes = getTypePack(L, 2, 3); + if (lua_istable(L, 4)) + setArgNames(L, 4, /* argLength */ lua_objlen(L, 2), tfft->argNames); + else if (!lua_isnoneornil(L, 4)) + luaL_typeerrorL(L, -1, "table"); + return 0; } -// Luau: `self:parameters() -> {head: {type}?, tail: type?}` +// Luau: `self:parameters() -> {head: {type}?, names: { [number]: type? }?, tail: type?}` // Returns the parameters of the function static int getFunctionParameters(lua_State* L) { @@ -1180,6 +1219,23 @@ static int getFunctionParameters(lua_State* L) pushTypePack(L, tfft->argTypes); + size_t argLength = tfft->argNames.size(); + + lua_createtable(L, argLength, 0); + + for (size_t i = 1; i <= argLength; ++i) { + lua_pushinteger(L, i); + + std::optional argName = tfft->argNames[i - 1]; + if (argName.has_value()) + lua_pushstring(L, argName.value().c_str()); + else + lua_pushnil(L); + + lua_settable(L, -3); + } + + lua_setfield(L, -2, "names"); return 1; } @@ -2397,7 +2453,7 @@ class TypeFunctionCloner else if (auto f = get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); - target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack, {}}); } else if (auto c = get(ty)) target = ty; // Don't copy a class since they are immutable @@ -2562,6 +2618,10 @@ class TypeFunctionCloner f2->argTypes = shallowClone(f1->argTypes); f2->retTypes = shallowClone(f1->retTypes); + + f2->argNames.reserve(f1->argNames.size()); + for (auto argName : f1->argNames) + f2->argNames.push_back(argName); } void cloneChildren(TypeFunctionExternType* c1, TypeFunctionExternType* c2) diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index e9b09d47b..a7104821f 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -204,7 +204,7 @@ class TypeFunctionSerializer else if (auto f = get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); - target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack, {}}); } else if (auto c = get(ty)) { @@ -407,6 +407,11 @@ class TypeFunctionSerializer f2->argTypes = shallowSerialize(f1->argTypes); f2->retTypes = shallowSerialize(f1->retTypes); + + f2->argNames.reserve(f1->argNames.size()); + for (auto argName : f1->argNames) + f2->argNames.push_back(argName ? std::optional{argName->name} + : std::nullopt); } void serializeChildren(const ExternType* c1, TypeFunctionExternType* c2) @@ -993,6 +998,11 @@ class TypeFunctionDeserializer if (f2->retTypes) f1->retTypes = shallowDeserialize(f2->retTypes); + + f1->argNames.reserve(f2->argNames.size()); + for (const auto& argName : f2->argNames) + f1->argNames.push_back(argName ? std::optional{{*argName, Location()}} + : std::nullopt); } void deserializeChildren(TypeFunctionExternType* c2, ExternType* c1) From ea61caff54b2428f64a01a64d9fb14904b8e6540 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Mon, 15 Sep 2025 21:39:10 -0300 Subject: [PATCH 02/12] Use `LUAU_FASTFLAG(LuauTypeFunctionFunctionParameterNames)` --- Analysis/include/Luau/TypeFunctionRuntime.h | 2 +- Analysis/src/TypeFunctionRuntime.cpp | 195 ++++++++++++++------ Analysis/src/TypeFunctionRuntimeBuilder.cpp | 21 ++- 3 files changed, 154 insertions(+), 64 deletions(-) diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index 05e03d003..f6a3beb7d 100644 --- a/Analysis/include/Luau/TypeFunctionRuntime.h +++ b/Analysis/include/Luau/TypeFunctionRuntime.h @@ -158,7 +158,7 @@ struct TypeFunctionFunctionType TypeFunctionTypePackId argTypes; TypeFunctionTypePackId retTypes; - std::vector> argNames; + std::vector> argNames; }; template diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index c055b1d24..a867ae907 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -21,6 +21,7 @@ #include LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) +LUAU_FASTFLAG(LuauTypeFunctionFunctionParameterNames) namespace Luau { @@ -1092,33 +1093,121 @@ static void pushTypePack(lua_State* L, TypeFunctionTypePackId tp) } } -void setArgNames(lua_State* L, int namesIdx, size_t argLength, std::vector>& argNames) +static TypeFunctionTypePackId getFunctionParametersTypePack(lua_State* L, int headIdx, int tailIdx, std::vector>& argNames) { - namesIdx = lua_absindex(L, namesIdx); - - argNames.resize(argLength); + TypeFunctionTypePackId result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); - for (size_t i = 1; i <= argLength; ++i) { - lua_pushinteger(L, i); - lua_gettable(L, namesIdx); + std::vector head; - std::optional argName; + if (lua_istable(L, headIdx)) + { + lua_pushvalue(L, headIdx); - if (!lua_isnil(L, -1)) + for (int i = 1; i <= lua_objlen(L, -1); i++) { - TypeFunctionTypeId ty = getTypeUserData(L, -1); + lua_pushinteger(L, i); + lua_gettable(L, -2); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } + else if (lua_istable(L, -1)) + { + lua_getfield(L, -1, "type"); + head.push_back(getTypeUserData(L, -1)); + lua_pop(L, 1); - if (auto tfst = get(ty)) - if (auto tfsst = get(tfst); tfsst && isIdentifier(tfsst->value)) - argName = tfsst->value; + lua_getfield(L, -1, "name"); + std::optional argName = std::nullopt; + if (lua_isstring(L, -1)) + { + std::string_view str = lua_tostring(L, -1); + if (Luau::isIdentifier(str)) + argName = FunctionArgument{ .name = std::string(str), .location = Location{} }; + } + argNames.push_back(argName); + lua_pop(L, 2); + } + else { + head.push_back(getTypeUserData(L, -1)); + argNames.push_back(std::nullopt); + lua_pop(L, 1); + } } - argNames[i - 1] = argName; lua_pop(L, 1); } + + std::optional tail; + + if (auto type = optionalTypeUserData(L, tailIdx)) + { + if (auto gty = get(*type); gty && gty->isPack) + tail = allocateTypeFunctionTypePack(L, TypeFunctionGenericTypePack{gty->isNamed, gty->name}); + else + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + } + + if (head.size() == 0 && tail.has_value()) + result = *tail; + else + result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{std::move(head), tail}); + + return result; } -// Luau: `types.newfunction(parameters: {head: {type}?, names: { [number]: type? }?, tail: type?}, returns: {head: {type}?, tail: type?}, generics: {type}?) -> type` +static void pushFunctionParametersTypePack(lua_State* L, const TypeFunctionFunctionType * tfft) +{ + if (auto tftp = get(tfft->argTypes)) + { + lua_createtable(L, 0, 2); + + if (!tftp->head.empty()) + { + lua_createtable(L, int(tftp->head.size()), 0); + int pos = 1; + + for (auto el : tftp->head) + { + lua_createtable(L, 0, 2); + if (pos <= tfft->argNames.size()) + { + auto argName = tfft->argNames.at(pos - 1); + if (argName.has_value()) + { + lua_pushstring(L, argName->name.c_str()); + lua_setfield(L, -2, "name"); + } + } + allocTypeUserData(L, el->type); + lua_setfield(L, -2, "type"); + lua_rawseti(L, -2, pos++); + } + + lua_setfield(L, -2, "head"); + } + + if (tftp->tail.has_value()) + { + if (auto tfvp = get(*tftp->tail)) + allocTypeUserData(L, tfvp->type->type); + else if (auto tfgp = get(*tftp->tail)) + allocTypeUserData(L, TypeFunctionGenericType{tfgp->isNamed, true, tfgp->name}); + else + luaL_error(L, "unsupported type pack type"); + + lua_setfield(L, -2, "tail"); + } + } + else + { + luaL_error(L, "unsupported type pack type"); + } +} + +// Luau: `types.newfunction(parameters: {head: {type | { name: string?, type: type }}?, tail: type?}, returns: {head: {type}?, tail: type?}, generics: {type}?) -> type` // Returns the type instance representing a function static int createFunction(lua_State* L) { @@ -1127,23 +1216,23 @@ static int createFunction(lua_State* L) luaL_error(L, "types.newfunction: expected 0-3 arguments, but got %d", argumentCount); TypeFunctionTypePackId argTypes = nullptr; - std::vector> argNames; + + std::vector> argNames = {}; if (lua_istable(L, 1)) { lua_getfield(L, 1, "head"); lua_getfield(L, 1, "tail"); - argTypes = getTypePack(L, -2, -1); - - lua_getfield(L, 1, "names"); - - if (lua_istable(L, -1)) - setArgNames(L, -1, /* argLength */ lua_objlen(L, -3), argNames); - else - luaL_typeerrorL(L, -1, "table"); + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + argTypes = getFunctionParametersTypePack(L, -2, -1, argNames); + } + else { + argTypes = getTypePack(L, -2, -1); + } - lua_pop(L, 3); + lua_pop(L, 2); } else if (!lua_isnoneornil(L, 1)) { @@ -1176,35 +1265,37 @@ static int createFunction(lua_State* L) auto [genericTypes, genericPacks] = getGenerics(L, 3, "types.newfunction"); - allocTypeUserData(L, TypeFunctionFunctionType{std::move(genericTypes), std::move(genericPacks), argTypes, retTypes, argNames}); + allocTypeUserData(L, TypeFunctionFunctionType{std::move(genericTypes), std::move(genericPacks), argTypes, retTypes}); return 1; } -// Luau: `self:setparameters(head: {type}?, tail: type?, names: { [number]: type? }?)` +// Luau: `self:setparameters(head: {type | { name: string?, type: type }}?, tail: type?)` // Sets the parameters of the function static int setFunctionParameters(lua_State* L) { int argumentCount = lua_gettop(L); - if (argumentCount > 4 || argumentCount < 1) - luaL_error(L, "type.setparameters: expected 1-4, but got %d", argumentCount); + if (argumentCount > 3 || argumentCount < 1) + luaL_error(L, "type.setparameters: expected 1-3, but got %d", argumentCount); TypeFunctionTypeId self = getTypeUserData(L, 1); auto tfft = getMutable(self); if (!tfft) luaL_error(L, "type.setparameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - tfft->argTypes = getTypePack(L, 2, 3); - - if (lua_istable(L, 4)) - setArgNames(L, 4, /* argLength */ lua_objlen(L, 2), tfft->argNames); - else if (!lua_isnoneornil(L, 4)) - luaL_typeerrorL(L, -1, "table"); + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + tfft->argTypes = getFunctionParametersTypePack(L, 2, 3, tfft->argNames); + } + else + { + tfft->argTypes = getTypePack(L, 2, 3); + } return 0; } -// Luau: `self:parameters() -> {head: {type}?, names: { [number]: type? }?, tail: type?}` +// Luau: `self:parameters() -> {head: {{ name: string?, type: type }}?, tail: type?}` // Returns the parameters of the function static int getFunctionParameters(lua_State* L) { @@ -1217,25 +1308,16 @@ static int getFunctionParameters(lua_State* L) if (!tfft) luaL_error(L, "type.parameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - pushTypePack(L, tfft->argTypes); - - size_t argLength = tfft->argNames.size(); - - lua_createtable(L, argLength, 0); - - for (size_t i = 1; i <= argLength; ++i) { - lua_pushinteger(L, i); - - std::optional argName = tfft->argNames[i - 1]; - if (argName.has_value()) - lua_pushstring(L, argName.value().c_str()); - else - lua_pushnil(L); - lua_settable(L, -3); + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + pushFunctionParametersTypePack(L, tfft); + } + else + { + pushTypePack(L, tfft->argTypes); } - lua_setfield(L, -2, "names"); return 1; } @@ -2619,9 +2701,12 @@ class TypeFunctionCloner f2->argTypes = shallowClone(f1->argTypes); f2->retTypes = shallowClone(f1->retTypes); - f2->argNames.reserve(f1->argNames.size()); - for (auto argName : f1->argNames) - f2->argNames.push_back(argName); + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + f2->argNames.reserve(f1->argNames.size()); + for (const auto& argName : f1->argNames) + f2->argNames.push_back(argName); + } } void cloneChildren(TypeFunctionExternType* c1, TypeFunctionExternType* c2) diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index a7104821f..5a9557f08 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -20,6 +20,7 @@ // currently, controls serialization, deserialization, and `type.copy` LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAG(LuauTypeFunctionFunctionParameterNames) namespace Luau { @@ -408,10 +409,12 @@ class TypeFunctionSerializer f2->argTypes = shallowSerialize(f1->argTypes); f2->retTypes = shallowSerialize(f1->retTypes); - f2->argNames.reserve(f1->argNames.size()); - for (auto argName : f1->argNames) - f2->argNames.push_back(argName ? std::optional{argName->name} - : std::nullopt); + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + f2->argNames.reserve(f1->argNames.size()); + for (const auto& argName : f1->argNames) + f2->argNames.push_back(argName); + } } void serializeChildren(const ExternType* c1, TypeFunctionExternType* c2) @@ -999,10 +1002,12 @@ class TypeFunctionDeserializer if (f2->retTypes) f1->retTypes = shallowDeserialize(f2->retTypes); - f1->argNames.reserve(f2->argNames.size()); - for (const auto& argName : f2->argNames) - f1->argNames.push_back(argName ? std::optional{{*argName, Location()}} - : std::nullopt); + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + f1->argNames.reserve(f2->argNames.size()); + for (const auto& argName : f2->argNames) + f1->argNames.push_back(argName); + } } void deserializeChildren(TypeFunctionExternType* c2, ExternType* c1) From dcf7ec7d54e85bd5fe9e3ade9efabd7b581a9e65 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:00:59 -0300 Subject: [PATCH 03/12] Fix errors `C7555` and `C4018` --- Analysis/src/TypeFunctionRuntime.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index a867ae907..d251aa772 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -1125,7 +1125,10 @@ static TypeFunctionTypePackId getFunctionParametersTypePack(lua_State* L, int he { std::string_view str = lua_tostring(L, -1); if (Luau::isIdentifier(str)) - argName = FunctionArgument{ .name = std::string(str), .location = Location{} }; + argName = FunctionArgument{ + std::string(str), + {} // Maybe get the location of the user-defined type function instead? I'm not sure how to do this or if it's possible at all. + }; } argNames.push_back(argName); lua_pop(L, 2); @@ -1167,12 +1170,12 @@ static void pushFunctionParametersTypePack(lua_State* L, const TypeFunctionFunct if (!tftp->head.empty()) { lua_createtable(L, int(tftp->head.size()), 0); - int pos = 1; + size_t pos = 1; for (auto el : tftp->head) { lua_createtable(L, 0, 2); - if (pos <= tfft->argNames.size()) + if (tfft->argNames.size() >= pos) { auto argName = tfft->argNames.at(pos - 1); if (argName.has_value()) From 64d013fceee16cfc93d526cae028aed335f16fa7 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:11:11 -0300 Subject: [PATCH 04/12] Use `LUAU_FASTFLAGVARIABLE(LuauTypeFunctionFunctionParameterNames)` in TypeFunctionRuntime.cpp --- Analysis/src/TypeFunctionRuntime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index d251aa772..1b44cb919 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -21,7 +21,7 @@ #include LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) -LUAU_FASTFLAG(LuauTypeFunctionFunctionParameterNames) +LUAU_FASTFLAGVARIABLE(LuauTypeFunctionFunctionParameterNames) namespace Luau { From dc5094008499a5d27661c9d376b8f94cff390906 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:21:50 -0300 Subject: [PATCH 05/12] Fix bug (?) Not sure if this will actually do it, since this warning doesn't show up at all when I compile it --- Analysis/src/TypeFunctionRuntime.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 1b44cb919..30b44bbb1 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -1170,12 +1170,12 @@ static void pushFunctionParametersTypePack(lua_State* L, const TypeFunctionFunct if (!tftp->head.empty()) { lua_createtable(L, int(tftp->head.size()), 0); - size_t pos = 1; + int pos = 1; for (auto el : tftp->head) { lua_createtable(L, 0, 2); - if (tfft->argNames.size() >= pos) + if (tfft->argNames.size() >= (size_t)pos) { auto argName = tfft->argNames.at(pos - 1); if (argName.has_value()) From 70504ecde46af3273f363a41fc57f7d02347a852 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:19:50 -0300 Subject: [PATCH 06/12] I don't know how I missed this --- Analysis/src/TypeFunctionRuntime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 30b44bbb1..cd21bd6b0 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -1268,7 +1268,7 @@ static int createFunction(lua_State* L) auto [genericTypes, genericPacks] = getGenerics(L, 3, "types.newfunction"); - allocTypeUserData(L, TypeFunctionFunctionType{std::move(genericTypes), std::move(genericPacks), argTypes, retTypes}); + allocTypeUserData(L, TypeFunctionFunctionType{std::move(genericTypes), std::move(genericPacks), argTypes, retTypes, argNames}); return 1; } From fa54c6f0d58b8061679af70f3091b20adff4a0e0 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Fri, 19 Sep 2025 22:31:10 -0300 Subject: [PATCH 07/12] Remove ugly push function --- Analysis/src/TypeFunctionRuntime.cpp | 72 +++++++--------------------- 1 file changed, 18 insertions(+), 54 deletions(-) diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index cd21bd6b0..226c5ff03 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -1161,55 +1161,6 @@ static TypeFunctionTypePackId getFunctionParametersTypePack(lua_State* L, int he return result; } -static void pushFunctionParametersTypePack(lua_State* L, const TypeFunctionFunctionType * tfft) -{ - if (auto tftp = get(tfft->argTypes)) - { - lua_createtable(L, 0, 2); - - if (!tftp->head.empty()) - { - lua_createtable(L, int(tftp->head.size()), 0); - int pos = 1; - - for (auto el : tftp->head) - { - lua_createtable(L, 0, 2); - if (tfft->argNames.size() >= (size_t)pos) - { - auto argName = tfft->argNames.at(pos - 1); - if (argName.has_value()) - { - lua_pushstring(L, argName->name.c_str()); - lua_setfield(L, -2, "name"); - } - } - allocTypeUserData(L, el->type); - lua_setfield(L, -2, "type"); - lua_rawseti(L, -2, pos++); - } - - lua_setfield(L, -2, "head"); - } - - if (tftp->tail.has_value()) - { - if (auto tfvp = get(*tftp->tail)) - allocTypeUserData(L, tfvp->type->type); - else if (auto tfgp = get(*tftp->tail)) - allocTypeUserData(L, TypeFunctionGenericType{tfgp->isNamed, true, tfgp->name}); - else - luaL_error(L, "unsupported type pack type"); - - lua_setfield(L, -2, "tail"); - } - } - else - { - luaL_error(L, "unsupported type pack type"); - } -} - // Luau: `types.newfunction(parameters: {head: {type | { name: string?, type: type }}?, tail: type?}, returns: {head: {type}?, tail: type?}, generics: {type}?) -> type` // Returns the type instance representing a function static int createFunction(lua_State* L) @@ -1311,14 +1262,27 @@ static int getFunctionParameters(lua_State* L) if (!tfft) luaL_error(L, "type.parameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); + pushTypePack(L, tfft->argTypes); if (FFlag::LuauTypeFunctionFunctionParameterNames) { - pushFunctionParametersTypePack(L, tfft); - } - else - { - pushTypePack(L, tfft->argTypes); + int pos = 1; + + for (const auto& arg : tfft->argNames) + { + lua_createtable(L, 0, 2); + + if (arg) + { + lua_pushstring(L, arg->name.c_str()); + lua_setfield(L, -2, "name"); + } + + lua_rawgeti(L, -2, pos); + lua_setfield(L, -2, "type"); + + lua_rawseti(L, -2, pos++); + } } return 1; From 347fee04a6626a2461dbbb8eb94c22ec145b0ecd Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Fri, 19 Sep 2025 23:37:24 -0300 Subject: [PATCH 08/12] Remove ugly get function --- Analysis/src/TypeFunctionRuntime.cpp | 112 ++++++++++----------------- 1 file changed, 41 insertions(+), 71 deletions(-) diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 226c5ff03..c369c0569 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -1015,9 +1015,17 @@ static TypeFunctionTypePackId getTypePack(lua_State* L, int headIdx, int tailIdx lua_pop(L, 1); break; } - - head.push_back(getTypeUserData(L, -1)); - lua_pop(L, 1); + else if (FFlag::LuauTypeFunctionFunctionParameterNames && lua_istable(L, -1)) + { + lua_getfield(L, -1, "type"); + head.push_back(getTypeUserData(L, -1)); + lua_pop(L, 2); + } + else + { + head.push_back(getTypeUserData(L, -1)); + lua_pop(L, 1); + } } lua_pop(L, 1); @@ -1093,75 +1101,40 @@ static void pushTypePack(lua_State* L, TypeFunctionTypePackId tp) } } -static TypeFunctionTypePackId getFunctionParametersTypePack(lua_State* L, int headIdx, int tailIdx, std::vector>& argNames) +static void getArgNames(lua_State* L, int headIdx, std::vector>& argNames) { - TypeFunctionTypePackId result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + if (!lua_istable(L, -2)) + return; - std::vector head; + int headLen = lua_objlen(L, headIdx); + argNames.resize(headLen); + headIdx = lua_absindex(L, headIdx); - if (lua_istable(L, headIdx)) + for (int i = 1; i <= headLen; i++) { - lua_pushvalue(L, headIdx); + lua_pushinteger(L, i); + lua_gettable(L, headIdx); - for (int i = 1; i <= lua_objlen(L, -1); i++) - { - lua_pushinteger(L, i); - lua_gettable(L, -2); + std::optional argName; - if (lua_isnil(L, -1)) - { - lua_pop(L, 1); - break; - } - else if (lua_istable(L, -1)) + if (lua_istable(L, -1)) + { + lua_getfield(L, -1, "name"); + if (lua_isstring(L, -1)) { - lua_getfield(L, -1, "type"); - head.push_back(getTypeUserData(L, -1)); - lua_pop(L, 1); - - lua_getfield(L, -1, "name"); - std::optional argName = std::nullopt; - if (lua_isstring(L, -1)) - { - std::string_view str = lua_tostring(L, -1); - if (Luau::isIdentifier(str)) - argName = FunctionArgument{ - std::string(str), - {} // Maybe get the location of the user-defined type function instead? I'm not sure how to do this or if it's possible at all. - }; - } - argNames.push_back(argName); - lua_pop(L, 2); - } - else { - head.push_back(getTypeUserData(L, -1)); - argNames.push_back(std::nullopt); - lua_pop(L, 1); + const char* str = lua_tostring(L, -1); + if (Luau::isIdentifier(str)) + argName = FunctionArgument{str, {}}; } + lua_pop(L, 1); } + argNames[i - 1] = argName; lua_pop(L, 1); } - - std::optional tail; - - if (auto type = optionalTypeUserData(L, tailIdx)) - { - if (auto gty = get(*type); gty && gty->isPack) - tail = allocateTypeFunctionTypePack(L, TypeFunctionGenericTypePack{gty->isNamed, gty->name}); - else - tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); - } - - if (head.size() == 0 && tail.has_value()) - result = *tail; - else - result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{std::move(head), tail}); - - return result; } -// Luau: `types.newfunction(parameters: {head: {type | { name: string?, type: type }}?, tail: type?}, returns: {head: {type}?, tail: type?}, generics: {type}?) -> type` +// Luau: `types.newfunction(parameters: {head: {type | {name: string?, type: type}}?, tail: type?}, returns: {head: {type | {type: type}}?, tail: type?}, generics: {type}?) -> type` // Returns the type instance representing a function static int createFunction(lua_State* L) { @@ -1171,19 +1144,18 @@ static int createFunction(lua_State* L) TypeFunctionTypePackId argTypes = nullptr; - std::vector> argNames = {}; + std::vector> argNames{}; if (lua_istable(L, 1)) { lua_getfield(L, 1, "head"); lua_getfield(L, 1, "tail"); + argTypes = getTypePack(L, -2, -1); + if (FFlag::LuauTypeFunctionFunctionParameterNames) { - argTypes = getFunctionParametersTypePack(L, -2, -1, argNames); - } - else { - argTypes = getTypePack(L, -2, -1); + getArgNames(L, -2, argNames); } lua_pop(L, 2); @@ -1224,7 +1196,7 @@ static int createFunction(lua_State* L) return 1; } -// Luau: `self:setparameters(head: {type | { name: string?, type: type }}?, tail: type?)` +// Luau: `self:setparameters(head: {type | {name: string?, type: type}}?, tail: type?)` // Sets the parameters of the function static int setFunctionParameters(lua_State* L) { @@ -1237,19 +1209,17 @@ static int setFunctionParameters(lua_State* L) if (!tfft) luaL_error(L, "type.setparameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); + tfft->argTypes = getTypePack(L, 2, 3); + if (FFlag::LuauTypeFunctionFunctionParameterNames) { - tfft->argTypes = getFunctionParametersTypePack(L, 2, 3, tfft->argNames); - } - else - { - tfft->argTypes = getTypePack(L, 2, 3); + getArgNames(L, -2, tfft->argNames); } return 0; } -// Luau: `self:parameters() -> {head: {{ name: string?, type: type }}?, tail: type?}` +// Luau: `self:parameters() -> {head: {{name: string?, type: type}}?, tail: type?}` // Returns the parameters of the function static int getFunctionParameters(lua_State* L) { @@ -1288,7 +1258,7 @@ static int getFunctionParameters(lua_State* L) return 1; } -// Luau: `self:setreturns(head: {type}?, tail: type?)` +// Luau: `self:setreturns(head: {type | {type: type}}?, tail: type?)` // Sets the returns of the function static int setFunctionReturns(lua_State* L) { From 61dd576d8658c5d8677bf20d9cf4f35a7c051521 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Sat, 20 Sep 2025 02:16:05 -0300 Subject: [PATCH 09/12] Fix bugs --- Analysis/src/TypeFunctionRuntime.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index c369c0569..1a49e54f9 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -1103,7 +1103,7 @@ static void pushTypePack(lua_State* L, TypeFunctionTypePackId tp) static void getArgNames(lua_State* L, int headIdx, std::vector>& argNames) { - if (!lua_istable(L, -2)) + if (!lua_istable(L, headIdx)) return; int headLen = lua_objlen(L, headIdx); @@ -1124,7 +1124,7 @@ static void getArgNames(lua_State* L, int headIdx, std::vectorargNames) @@ -1253,6 +1255,8 @@ static int getFunctionParameters(lua_State* L) lua_rawseti(L, -2, pos++); } + + lua_pop(L, 1); } return 1; From 7e90ec26fb3f61d0860306f4023762ad42d1a6db Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Sat, 20 Sep 2025 02:34:37 -0300 Subject: [PATCH 10/12] Add type definitions --- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 85 +++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 8bb1a4ca2..fc426a2f7 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -3,6 +3,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp) LUAU_FASTFLAGVARIABLE(LuauRawGetHandlesNil) +LUAU_FASTFLAG(LuauTypeFunctionFunctionParameterNames) namespace Luau { @@ -455,6 +456,59 @@ export type type = { )BUILTIN_SRC"; +static constexpr const char* kBuiltinDefinitionTypeMethodSrc_PARAMETER_NAMES = R"BUILTIN_SRC( + +export type type = { + tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" | + "singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "generic", + + is: (self: type, arg: string) -> boolean, + + -- for singleton type + value: (self: type) -> (string | boolean | nil), + + -- for negation type + inner: (self: type) -> type, + + -- for union and intersection types + components: (self: type) -> {type}, + + -- for table type + setproperty: (self: type, key: type, value: type?) -> (), + setreadproperty: (self: type, key: type, value: type?) -> (), + setwriteproperty: (self: type, key: type, value: type?) -> (), + readproperty: (self: type, key: type) -> type?, + writeproperty: (self: type, key: type) -> type?, + properties: (self: type) -> { [type]: { read: type?, write: type? } }, + setindexer: (self: type, index: type, result: type) -> (), + setreadindexer: (self: type, index: type, result: type) -> (), + setwriteindexer: (self: type, index: type, result: type) -> (), + indexer: (self: type) -> { index: type, readresult: type, writeresult: type }?, + readindexer: (self: type) -> { index: type, result: type }?, + writeindexer: (self: type) -> { index: type, result: type }?, + setmetatable: (self: type, arg: type) -> (), + metatable: (self: type) -> type?, + + -- for function type + setparameters: (self: type, head: {type | { name: string?, type: type }}?, tail: type?) -> (), + parameters: (self: type) -> { head: {{ name: string?, type: type }}?, tail: type? }, + setreturns: (self: type, head: {type | { type: type }}?, tail: type? ) -> (), + returns: (self: type) -> { head: {type}?, tail: type? }, + setgenerics: (self: type, {type}?) -> (), + generics: (self: type) -> {type}, + + -- for class type + -- 'properties', 'metatable', 'indexer', 'readindexer' and 'writeindexer' are shared with table type + readparent: (self: type) -> type?, + writeparent: (self: type) -> type?, + + -- for generic type + name: (self: type) -> string?, + ispack: (self: type) -> boolean, +} + +)BUILTIN_SRC"; + static constexpr const char* kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC( declare types: { @@ -479,9 +533,40 @@ declare types: { } )BUILTIN_SRC"; +static constexpr const char* kBuiltinDefinitionTypesLibSrc_PARAMETER_NAMES = R"BUILTIN_SRC( + +declare types: { + unknown: type, + never: type, + any: type, + boolean: type, + number: type, + string: type, + thread: type, + buffer: type, + + singleton: @checked (arg: string | boolean | nil) -> type, + optional: @checked (arg: type) -> type, + generic: @checked (name: string, ispack: boolean?) -> type, + negationof: @checked (arg: type) -> type, + unionof: @checked (...type) -> type, + intersectionof: @checked (...type) -> type, + newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type, + newfunction: @checked (parameters: { head: {type | { name: string?, type: type }}?, tail: type? }?, returns: { head: {type | { type: type }}?, tail: type? }?, generics: {type}?) -> type, + copy: @checked (arg: type) -> type, +} +)BUILTIN_SRC"; std::string getTypeFunctionDefinitionSource() { + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + std::string result = kBuiltinDefinitionTypeMethodSrc_PARAMETER_NAMES; + + result += kBuiltinDefinitionTypesLibSrc_PARAMETER_NAMES; + + return result; + } std::string result = kBuiltinDefinitionTypeMethodSrc; From b935e2f70a7f2ec828711632c44c99a83de3b6b9 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Sat, 20 Sep 2025 08:46:16 -0300 Subject: [PATCH 11/12] Don't change returns --- Analysis/src/TypeFunctionRuntime.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 1a49e54f9..a7fe4f4e2 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -995,7 +995,7 @@ static std::tuple, std::vectorargTypes = getTypePack(L, 2, 3); + tfft->argTypes = getTypePack(L, 2, 3, /* namedTypePack */ true); if (FFlag::LuauTypeFunctionFunctionParameterNames) { @@ -1262,7 +1262,7 @@ static int getFunctionParameters(lua_State* L) return 1; } -// Luau: `self:setreturns(head: {type | {type: type}}?, tail: type?)` +// Luau: `self:setreturns(head: {type}?, tail: type?)` // Sets the returns of the function static int setFunctionReturns(lua_State* L) { From a8a1275c7f17aaaf7c4d2e308e304c303db4079f Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Sat, 20 Sep 2025 08:50:30 -0300 Subject: [PATCH 12/12] Add parameter type in type definitions --- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 59 +++++++++++---------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index fc426a2f7..147af1e4f 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -456,8 +456,34 @@ export type type = { )BUILTIN_SRC"; +static constexpr const char* kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC( + +declare types: { + unknown: type, + never: type, + any: type, + boolean: type, + number: type, + string: type, + thread: type, + buffer: type, + + singleton: @checked (arg: string | boolean | nil) -> type, + optional: @checked (arg: type) -> type, + generic: @checked (name: string, ispack: boolean?) -> type, + negationof: @checked (arg: type) -> type, + unionof: @checked (...type) -> type, + intersectionof: @checked (...type) -> type, + newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type, + newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type, + copy: @checked (arg: type) -> type, +} +)BUILTIN_SRC"; + static constexpr const char* kBuiltinDefinitionTypeMethodSrc_PARAMETER_NAMES = R"BUILTIN_SRC( +type Parameter = { name: string?, type: type } + export type type = { tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" | "singleton" | "negation" | "union" | "intersection" | "table" | "function" | "class" | "generic", @@ -490,9 +516,9 @@ export type type = { metatable: (self: type) -> type?, -- for function type - setparameters: (self: type, head: {type | { name: string?, type: type }}?, tail: type?) -> (), - parameters: (self: type) -> { head: {{ name: string?, type: type }}?, tail: type? }, - setreturns: (self: type, head: {type | { type: type }}?, tail: type? ) -> (), + setparameters: (self: type, head: {type | { type: type } | Parameter}?, tail: type?) -> (), + parameters: (self: type) -> { head: {Parameter}?, tail: type? }, + setreturns: (self: type, head: {type}?, tail: type? ) -> (), returns: (self: type) -> { head: {type}?, tail: type? }, setgenerics: (self: type, {type}?) -> (), generics: (self: type) -> {type}, @@ -509,30 +535,6 @@ export type type = { )BUILTIN_SRC"; -static constexpr const char* kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC( - -declare types: { - unknown: type, - never: type, - any: type, - boolean: type, - number: type, - string: type, - thread: type, - buffer: type, - - singleton: @checked (arg: string | boolean | nil) -> type, - optional: @checked (arg: type) -> type, - generic: @checked (name: string, ispack: boolean?) -> type, - negationof: @checked (arg: type) -> type, - unionof: @checked (...type) -> type, - intersectionof: @checked (...type) -> type, - newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type, - newfunction: @checked (parameters: { head: {type}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type, - copy: @checked (arg: type) -> type, -} -)BUILTIN_SRC"; - static constexpr const char* kBuiltinDefinitionTypesLibSrc_PARAMETER_NAMES = R"BUILTIN_SRC( declare types: { @@ -552,9 +554,10 @@ declare types: { unionof: @checked (...type) -> type, intersectionof: @checked (...type) -> type, newtable: @checked (props: {[type]: type} | {[type]: { read: type, write: type } } | nil, indexer: { index: type, readresult: type, writeresult: type }?, metatable: type?) -> type, - newfunction: @checked (parameters: { head: {type | { name: string?, type: type }}?, tail: type? }?, returns: { head: {type | { type: type }}?, tail: type? }?, generics: {type}?) -> type, + newfunction: @checked (parameters: { head: {type | Parameter}?, tail: type? }?, returns: { head: {type}?, tail: type? }?, generics: {type}?) -> type, copy: @checked (arg: type) -> type, } + )BUILTIN_SRC"; std::string getTypeFunctionDefinitionSource()