From e56c2b4213833be308895722fc33c2737ff24b6b Mon Sep 17 00:00:00 2001 From: Tenebris Noctua <172976441+TenebrisNoctua@users.noreply.github.com> Date: Sat, 12 Apr 2025 17:17:36 +0300 Subject: [PATCH 1/3] Create support-for-type-packs-in-user-defined-type-functions.md --- ...pe-packs-in-user-defined-type-functions.md | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 docs/support-for-type-packs-in-user-defined-type-functions.md diff --git a/docs/support-for-type-packs-in-user-defined-type-functions.md b/docs/support-for-type-packs-in-user-defined-type-functions.md new file mode 100644 index 0000000..0f81d17 --- /dev/null +++ b/docs/support-for-type-packs-in-user-defined-type-functions.md @@ -0,0 +1,119 @@ +# Support for Type Packs in User-Defined Type Functions + +## Summary + +Add support for type packs in user defined type functions. + +## Motivation + +Currently, user-defined type functions are limited in terms of support for type packs. The only place where a type pack is represented as a runtime type, is within generic `type` instances, +where the instance can either be a generic type, or a generic type pack. Other kinds of packs such as variadic packs can only be created within 'tail' spots. + +This limits the usage for packs as they are used often in the language, and their creation and usage should be easier and more expanded upon. + +## Design + +We introduce a new runtime type called `pack` to extend the support of type packs within the type runtime. + +Packs will have a `kind` property that specifies the kind of the pack. A pack can either be a `list` kind or a `variadic` kind. + +For example, consider a variadic type pack (e.g `...number`), in the new pack type, it will be represented as a `pack` with a `variadic` kind. +Or for a normal type pack such as `(number, string)`, it will be represented as a `pack` with a `list` kind. + +`list` kind of packs will be able to contain other packs, or types more than one, whereas `variadic` type of packs will only contain *one* type. + +The user will be able to obtain the types within `pack`s using the `:unpack()` method, this method will return a table containing all the types or type packs within the pack. +In `variadic` kind of `pack`s, `:unpack()` will only return a table with one element. This is because variadic packs are essentially many elements of the same type. + +In user-defined type functions, type packs can be created by providing types or type packs that will be included within the pack. A pack will also be able to contain other packs, even packs of different kinds. + +### Examples + +Creating a type function that accepts type packs: + +```luau +type function f(arg: type) + if not arg:is("pack") then error("The given argument is not a type pack!") end + local unpacked = arg:unpack() -- {string, number} + print(unpacked) +end +type a = f<(string, number)> +``` + +Creating a type function that returns a function with a type pack: + +```luau +type function f() + local newListPack = types.pack({types.string, types.number}) + local newVariadicPack = types.pack({types.any}, true) + local newfunc = types.newfunction() + newfunc:setparameters({newPack}, newVariadicPack) + newfunc:setreturns({newPack}, newVariadicPack) + return newfunc +end +type A = f<> -- (string, number, ...any) -> (string, number, ...any) +``` + +The pack type will also enable generic type packs to be provided to type functions from aliases in the type runtime. +Generic type pack arguments will pass the given type pack to the type function. + +```luau +type function createCallback(args: type, returns: type) -- args will be a list pack, returns will be a variadic pack. + local newTable = types.newtable() + local newfunc = types.newfunction() + newfunc:setparameters({args}) + newfunc:setreturns({returns}) + newTable:setproperty(types.singleton("f"), newfunc) + return newTable +end +type Callback = createCallback +type A = Callback<(number, string), ...number> +``` + +If variadic type functions were to be implemented, type packs could be retrieved like this: + +```luau +type function f(...) + local packed = {...} -- {type} + local firstPack = packed[1] -- type.kind: list + local secondPack = packed[2] -- type.kind: variadic +end +type a = f<(string, number), ...number> +``` + +## Updates to the types library and type userdata + +### `types` Library + +| New/Update | Library Functions | Return Type | Description | +| ------------- | ------------- | ------------- | ------------- | +| New | `pack(types: {type} isvariadic: boolean?)` | `type` | returns an immutable instance of a type pack; when `isvariadic` is true, `types` table can only have one type. | +| Update | `newfunction(parameters: { head: {type}?, tail: type? }, returns: { head: {type}?, tail: type? }, generics: {type}?)` | `type` | `tail` arguments can now accept variadic packs. + +### `type` Instance + +| New/Update | Instance Properties | Type | Description | +| ------------- | ------------- | ------------- | ------------- | +| Update | `tag` | `"nil" \| "unknown" \| "never" \| "any" \| "boolean" \| "number" \| "string" \| "singleton" \| "negation" \| "union" \| "intersection" \| "table" \| "function" \| "class" \| "thread" \| "buffer" \| "generic" \| "pack"` | Added `pack` as a possible tag | + +#### Pack `type` instance + +| New/Update | Instance Methods | Type | Description | +| ------------- | ------------- | ------------- | ------------- | +| New | `kind` | `"list" \| "variadic"` | indicates the kind of the pack. | +| New | `unpack()` | `{type}` | returns the types contained within the pack, returns only one element if `kind` is `variadic`. | + +#### Function `type` instance + +| New/Update | Instance Methods | Return Type | Description | +| ------------- | ------------- | ------------- | ------------- | +| Update | `setparameters(head: {type}?, tail: type?)` | `()` | `tail` argument can now accept variadic packs. | +| Update | `setreturns(head: {type}?, tail: type?)` | `()` | `tail` argument can now accept variadic packs. | + +## Drawbacks + +This may bring additional complexity to type functions, and now variadic types will no longer have special meanings only in a tail spot. + +## Alternatives + +Do nothing and force type functions to only accept singular type parameters. From 94304b2e57872b24632ea531212ba215c3c89fbc Mon Sep 17 00:00:00 2001 From: Tenebris Noctua <172976441+TenebrisNoctua@users.noreply.github.com> Date: Sun, 13 Apr 2025 18:33:09 +0300 Subject: [PATCH 2/3] Update support-for-type-packs-in-user-defined-type-functions.md Major change: Separated packs from the type userdata since packs are not types. --- ...pe-packs-in-user-defined-type-functions.md | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/docs/support-for-type-packs-in-user-defined-type-functions.md b/docs/support-for-type-packs-in-user-defined-type-functions.md index 0f81d17..7d03e75 100644 --- a/docs/support-for-type-packs-in-user-defined-type-functions.md +++ b/docs/support-for-type-packs-in-user-defined-type-functions.md @@ -13,16 +13,16 @@ This limits the usage for packs as they are used often in the language, and thei ## Design -We introduce a new runtime type called `pack` to extend the support of type packs within the type runtime. +We introduce a new userdata called `pack` alongside `type` to extend the support of type packs within the type runtime. -Packs will have a `kind` property that specifies the kind of the pack. A pack can either be a `list` kind or a `variadic` kind. +`pack` instances will have a `kind` property that specifies the kind of the pack. A `pack` can either be of a `literal` kind or of a `variadic` kind. For example, consider a variadic type pack (e.g `...number`), in the new pack type, it will be represented as a `pack` with a `variadic` kind. -Or for a normal type pack such as `(number, string)`, it will be represented as a `pack` with a `list` kind. +Or for a normal type pack such as `(number, string)`, it will be represented as a `pack` with a `literal` kind. -`list` kind of packs will be able to contain other packs, or types more than one, whereas `variadic` type of packs will only contain *one* type. +`literal` packs will be able to contain other packs, or types more than one, whereas `variadic` kind of packs will only contain *one* type. -The user will be able to obtain the types within `pack`s using the `:unpack()` method, this method will return a table containing all the types or type packs within the pack. +The user will be able to obtain the `type`s within `pack`s using the `:unpack()` method, this method will return a table containing all the `type`s or `pack`s within the `pack`. In `variadic` kind of `pack`s, `:unpack()` will only return a table with one element. This is because variadic packs are essentially many elements of the same type. In user-defined type functions, type packs can be created by providing types or type packs that will be included within the pack. A pack will also be able to contain other packs, even packs of different kinds. @@ -32,10 +32,9 @@ In user-defined type functions, type packs can be created by providing types or Creating a type function that accepts type packs: ```luau -type function f(arg: type) - if not arg:is("pack") then error("The given argument is not a type pack!") end +type function f(arg: pack) + if typeof(arg) ~= "pack" then error("The given argument is not a type pack!") end local unpacked = arg:unpack() -- {string, number} - print(unpacked) end type a = f<(string, number)> ``` @@ -44,8 +43,9 @@ Creating a type function that returns a function with a type pack: ```luau type function f() - local newListPack = types.pack({types.string, types.number}) - local newVariadicPack = types.pack({types.any}, true) + local newPack = types.pack({types.string, types.number}) -- (string, number) + local newVariadicPack = types.pack({types.any}, true) -- ...any + local newfunc = types.newfunction() newfunc:setparameters({newPack}, newVariadicPack) newfunc:setreturns({newPack}, newVariadicPack) @@ -58,7 +58,7 @@ The pack type will also enable generic type packs to be provided to type functio Generic type pack arguments will pass the given type pack to the type function. ```luau -type function createCallback(args: type, returns: type) -- args will be a list pack, returns will be a variadic pack. +type function createCallback(args: pack, returns: pack) -- args will be a literal pack, returns will be a variadic pack. local newTable = types.newtable() local newfunc = types.newfunction() newfunc:setparameters({args}) @@ -74,35 +74,40 @@ If variadic type functions were to be implemented, type packs could be retrieved ```luau type function f(...) - local packed = {...} -- {type} - local firstPack = packed[1] -- type.kind: list - local secondPack = packed[2] -- type.kind: variadic + local packed = {...} -- {pack} + local firstPack = packed[1] -- pack.kind: literal + local secondPack = packed[2] -- pack.kind: variadic end type a = f<(string, number), ...number> ``` -## Updates to the types library and type userdata +Evaluating `typeof(...)` on a `pack` userdata will give the string `"pack"`. + +```luau +type function f(arg: pack) + print(typeof(arg)) -- "pack" +end +type a = f<(string, number)> +``` + +## New pack userdata + +### `pack` Instance + +| New/Update | Instance Properties & Methods | Type | Description | +| ------------- | ------------- | ------------- | ------------- | +| New | `kind` | `"literal" \| "variadic"` | indicates the kind of the `pack`. | +| New | `unpack()` | `{pack \| type}` | returns the `type`s contained within the `pack`, returns only one element if `kind` is `variadic`. | + +## Updates to the types library and the type userdata ### `types` Library | New/Update | Library Functions | Return Type | Description | | ------------- | ------------- | ------------- | ------------- | -| New | `pack(types: {type} isvariadic: boolean?)` | `type` | returns an immutable instance of a type pack; when `isvariadic` is true, `types` table can only have one type. | +| New | `pack(args: {pack \| type}, isvariadic: boolean?)` | `pack` | returns an immutable instance of a type pack; when `isvariadic` is true, `args` table can only have one `type`. | | Update | `newfunction(parameters: { head: {type}?, tail: type? }, returns: { head: {type}?, tail: type? }, generics: {type}?)` | `type` | `tail` arguments can now accept variadic packs. -### `type` Instance - -| New/Update | Instance Properties | Type | Description | -| ------------- | ------------- | ------------- | ------------- | -| Update | `tag` | `"nil" \| "unknown" \| "never" \| "any" \| "boolean" \| "number" \| "string" \| "singleton" \| "negation" \| "union" \| "intersection" \| "table" \| "function" \| "class" \| "thread" \| "buffer" \| "generic" \| "pack"` | Added `pack` as a possible tag | - -#### Pack `type` instance - -| New/Update | Instance Methods | Type | Description | -| ------------- | ------------- | ------------- | ------------- | -| New | `kind` | `"list" \| "variadic"` | indicates the kind of the pack. | -| New | `unpack()` | `{type}` | returns the types contained within the pack, returns only one element if `kind` is `variadic`. | - #### Function `type` instance | New/Update | Instance Methods | Return Type | Description | From fcc1c274b5886f4c127af4d8a122a7f3c932e1c2 Mon Sep 17 00:00:00 2001 From: Tenebris Noctua <172976441+TenebrisNoctua@users.noreply.github.com> Date: Sun, 13 Apr 2025 23:18:16 +0300 Subject: [PATCH 3/3] Update support-for-type-packs-in-user-defined-type-functions.md Major update: Updated function type support for pack types. --- ...pe-packs-in-user-defined-type-functions.md | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/docs/support-for-type-packs-in-user-defined-type-functions.md b/docs/support-for-type-packs-in-user-defined-type-functions.md index 7d03e75..316fdd2 100644 --- a/docs/support-for-type-packs-in-user-defined-type-functions.md +++ b/docs/support-for-type-packs-in-user-defined-type-functions.md @@ -36,24 +36,24 @@ type function f(arg: pack) if typeof(arg) ~= "pack" then error("The given argument is not a type pack!") end local unpacked = arg:unpack() -- {string, number} end -type a = f<(string, number)> +type A = f<(string, number)> ``` -Creating a type function that returns a function with a type pack: +Type-packs can now be placed at the `tail` argument of function types: ```luau type function f() - local newPack = types.pack({types.string, types.number}) -- (string, number) - local newVariadicPack = types.pack({types.any}, true) -- ...any - + local newPack = types.pack({types.string, types.number, types.pack({any}, true)}) -- (string, number, ...any) local newfunc = types.newfunction() - newfunc:setparameters({newPack}, newVariadicPack) - newfunc:setreturns({newPack}, newVariadicPack) + newfunc:setparameters({}, newPack) + newfunc:setreturns({}, newPack) return newfunc end type A = f<> -- (string, number, ...any) -> (string, number, ...any) ``` +*To maintain backwards-compatibility, `tail` argument will still be able to take `type`s.* + The pack type will also enable generic type packs to be provided to type functions from aliases in the type runtime. Generic type pack arguments will pass the given type pack to the type function. @@ -61,13 +61,22 @@ Generic type pack arguments will pass the given type pack to the type function. type function createCallback(args: pack, returns: pack) -- args will be a literal pack, returns will be a variadic pack. local newTable = types.newtable() local newfunc = types.newfunction() - newfunc:setparameters({args}) - newfunc:setreturns({returns}) + newfunc:setparameters({}, args) + newfunc:setreturns({}, returns) newTable:setproperty(types.singleton("f"), newfunc) return newTable end type Callback = createCallback -type A = Callback<(number, string), ...number> +type A = Callback<(number, string), ...number> -- { f: (number, string) -> ...number } +``` + +Evaluating `typeof(...)` on a `pack` userdata will give the string `"pack"`. + +```luau +type function f(arg: pack) + print(typeof(arg)) -- "pack" +end +type a = f<(string, number)> ``` If variadic type functions were to be implemented, type packs could be retrieved like this: @@ -81,15 +90,6 @@ end type a = f<(string, number), ...number> ``` -Evaluating `typeof(...)` on a `pack` userdata will give the string `"pack"`. - -```luau -type function f(arg: pack) - print(typeof(arg)) -- "pack" -end -type a = f<(string, number)> -``` - ## New pack userdata ### `pack` Instance @@ -106,18 +106,21 @@ type a = f<(string, number)> | New/Update | Library Functions | Return Type | Description | | ------------- | ------------- | ------------- | ------------- | | New | `pack(args: {pack \| type}, isvariadic: boolean?)` | `pack` | returns an immutable instance of a type pack; when `isvariadic` is true, `args` table can only have one `type`. | -| Update | `newfunction(parameters: { head: {type}?, tail: type? }, returns: { head: {type}?, tail: type? }, generics: {type}?)` | `type` | `tail` arguments can now accept variadic packs. +| Update | `newfunction(parameters: { head: {type}?, tail: type \| pack? }, returns: { head: {type}?, tail: type \| pack? }, generics: {type}?)` | `type` | `tail` arguments can now accept packs. #### Function `type` instance | New/Update | Instance Methods | Return Type | Description | | ------------- | ------------- | ------------- | ------------- | -| Update | `setparameters(head: {type}?, tail: type?)` | `()` | `tail` argument can now accept variadic packs. | -| Update | `setreturns(head: {type}?, tail: type?)` | `()` | `tail` argument can now accept variadic packs. | +| Update | `setparameters(head: {type}?, tail: type \| pack?)` | `()` | `tail` argument can now accept packs. | +| Update | `setreturns(head: {type}?, tail: type \| pack?)` | `()` | `tail` argument can now accept packs. | ## Drawbacks -This may bring additional complexity to type functions, and now variadic types will no longer have special meanings only in a tail spot. +This may bring additional complexity to type functions, and since we're bringing a new userdata to the runtime, type functions now get two types of parameters, `type` or `pack`. +This doesn't pose a problem when the arguments are annotated (`arg: type`), but in unannotated arguments (`arg`), this may pose a problem. + +To solve this problem, inference can treat all provided arguments as `type`, until it encounters a `pack` related function. ## Alternatives