diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index 6b198c723..f6a3beb7d 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/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 8bb1a4ca2..147af1e4f 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 { @@ -479,9 +480,96 @@ declare types: { } )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", + + 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 | { 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}, + + -- 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_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 | Parameter}?, tail: type? }?, returns: { head: {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; diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 2dfd21d54..a7fe4f4e2 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -21,6 +21,7 @@ #include LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) +LUAU_FASTFLAGVARIABLE(LuauTypeFunctionFunctionParameterNames) namespace Luau { @@ -994,7 +995,7 @@ static std::tuple, std::vector type` +static void getArgNames(lua_State* L, int headIdx, std::vector>& argNames) +{ + if (!lua_istable(L, headIdx)) + return; + + int headLen = lua_objlen(L, headIdx); + argNames.resize(headLen); + headIdx = lua_absindex(L, headIdx); + + for (int i = 1; i <= headLen; i++) + { + lua_pushinteger(L, i); + lua_gettable(L, headIdx); + + std::optional argName; + + if (lua_istable(L, -1)) + { + lua_getfield(L, -1, "name"); + if (lua_isstring(L, -1)) + { + const char* str = lua_tostring(L, -1); + if (Luau::isIdentifier(str)) + argName = FunctionArgument{std::string(str), {}}; + } + lua_pop(L, 1); + } + + argNames[i - 1] = argName; + lua_pop(L, 1); + } +} + +// 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) { @@ -1102,12 +1144,19 @@ static int createFunction(lua_State* L) TypeFunctionTypePackId argTypes = nullptr; + std::vector> argNames{}; + if (lua_istable(L, 1)) { lua_getfield(L, 1, "head"); lua_getfield(L, 1, "tail"); - argTypes = getTypePack(L, -2, -1); + argTypes = getTypePack(L, -2, -1, /* namedTypePack */ true); + + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + getArgNames(L, -2, argNames); + } lua_pop(L, 2); } @@ -1142,12 +1191,12 @@ 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 | {name: string?, type: type}}?, tail: type?)` // Sets the parameters of the function static int setFunctionParameters(lua_State* L) { @@ -1160,12 +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); + tfft->argTypes = getTypePack(L, 2, 3, /* namedTypePack */ true); + + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + getArgNames(L, -2, tfft->argNames); + } return 0; } -// Luau: `self:parameters() -> {head: {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) { @@ -1180,6 +1234,31 @@ static int getFunctionParameters(lua_State* L) pushTypePack(L, tfft->argTypes); + if (FFlag::LuauTypeFunctionFunctionParameterNames) + { + lua_getfield(L, -1, "head"); + + 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++); + } + + lua_pop(L, 1); + } + return 1; } @@ -2397,7 +2476,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 +2641,13 @@ class TypeFunctionCloner f2->argTypes = shallowClone(f1->argTypes); f2->retTypes = shallowClone(f1->retTypes); + + 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 e9b09d47b..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 { @@ -204,7 +205,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 +408,13 @@ class TypeFunctionSerializer f2->argTypes = shallowSerialize(f1->argTypes); f2->retTypes = shallowSerialize(f1->retTypes); + + 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) @@ -993,6 +1001,13 @@ class TypeFunctionDeserializer if (f2->retTypes) f1->retTypes = shallowDeserialize(f2->retTypes); + + 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)