From 141ef75f2e10f405a3eeeb4a6b09985d14495438 Mon Sep 17 00:00:00 2001 From: Matheus <175355178+matlneus@users.noreply.github.com> Date: Thu, 28 Aug 2025 19:22:41 -0300 Subject: [PATCH 1/4] Initial commit --- docs/udtf-function-parameter-names.md | 102 ++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 docs/udtf-function-parameter-names.md diff --git a/docs/udtf-function-parameter-names.md b/docs/udtf-function-parameter-names.md new file mode 100644 index 000000000..5d37e9564 --- /dev/null +++ b/docs/udtf-function-parameter-names.md @@ -0,0 +1,102 @@ +# Function Parameter Names in User-Defined Type Functions + +## Summary + +This RFC proposes adding a method to include names for function parameters created in user-defined type functions. + +## Motivation + +Being able to use parameter names in user-defined type functions increases the expressiveness of the type system, which is the primary motivation of the original [user-defined type functions RFC](https://github.com/luau-lang/rfcs/pull/45). + +One could postulate that anything that can be written by hand in Luau should also be able to be written programmatically in a user-defined type function, and vice versa. Function parameter names currently don't meet this criteria. + +Here is an example and use case: + +```luau +type function Fire(Event: type): type + if not Event:is("function") then + return types.any + end + local Parameters = Event:parameters() + local Self = EventHandler(types.any) + if Parameters.head then + table.insert(Parameters.head, 1, Self) + else + Parameters.head = {Self} + end + local SelfName = types.singleton("self") + if Parameters.names then + table.insert(Parameters.names, 1, SelfName) + else + Parameters.names = {SelfName} + end + return types.newfunction(Parameters, Event:returns(), Event:generics()) +end + +export type EventHandler ()> = { + -- .. snip .. + read fire: Fire, + -- .. snip .. +} + +local my_event: EventHandler<(my_message: string) -> ()> + +my_event:fire("Hello, world!") -- function EventHandler:fire(self: { read fire: any }, my_message: string): () +``` + +## Design + +The following changes need to be made to the user-defined type functions API: + +### `types.newfunction` + +The `parameters` argument gains an optional `names` field, which acts as a map from parameter position (in the `head` array) to a string singleton type, or `nil`. + +- A string singleton type is used instead of just a string for consistency with the table key API and to support future extensions (for example, doc comments). +- Names that are `nil`, not string singleton types, or invalid Luau identifiers should be ignored. + +```luau +function types.newfunction( + parameters: { + head: {type}?, + names: { [number]: type }?, -- Doesn't need to be a contiguous array + tail: type? + }, + returns: { head: {type}?, tail: type? }, -- Return values don't have names (yet? 🤫) + generics: {type}? +) + -- .. snip .. +end +``` + +### `functiontype:setparameters` + +This method gains an optional `names` argument, following the same rules as above. + +```luau +function functiontype:setparameters( + head: {type}?, + tail: type?, + names: { [number]: type }?, +) -> () + -- .. snip .. +end +``` + +### `functiontype:parameters` + +The returned table gains an optional `names` field, following the same rules as above. Furthermore, missing/invalid parameter names are always `nil` (and not some other representation, such as string singleton types with string length 0). + +```luau +function functiontype:parameters(): { + head: {type}?, + names: { [number]: type }?, + tail: type? +}, + -- .. snip .. +end +``` + +## Drawbacks + +It's unclear if there are any drawbacks to this. Everything should be backwards compatible, and the motivation seems to align well enough with the goals of the language. From 4224e292cdd4ffc916808cb109ec8e7b41b50b9e Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Fri, 19 Sep 2025 21:20:17 -0300 Subject: [PATCH 2/4] Update user-defined type functions API --- docs/udtf-function-parameter-names.md | 93 ++++++++------------------- 1 file changed, 26 insertions(+), 67 deletions(-) diff --git a/docs/udtf-function-parameter-names.md b/docs/udtf-function-parameter-names.md index 5d37e9564..c8eba97f6 100644 --- a/docs/udtf-function-parameter-names.md +++ b/docs/udtf-function-parameter-names.md @@ -2,47 +2,15 @@ ## Summary -This RFC proposes adding a method to include names for function parameters created in user-defined type functions. +This RFC proposes a method to add names to function parameters in user-defined type functions. ## Motivation -Being able to use parameter names in user-defined type functions increases the expressiveness of the type system, which is the primary motivation of the original [user-defined type functions RFC](https://github.com/luau-lang/rfcs/pull/45). +Any type that can be written by hand in Luau should also be able to be written programmatically in a user-defined type function. Functions with parameter names currently don't meet this criteria. -One could postulate that anything that can be written by hand in Luau should also be able to be written programmatically in a user-defined type function, and vice versa. Function parameter names currently don't meet this criteria. +Furthermore, since parameter names currently aren't serialized in the type function runtime, *any* function type that goes through a user-defined type function loses that semantic information, which can be crucial to make the function self-describable. -Here is an example and use case: - -```luau -type function Fire(Event: type): type - if not Event:is("function") then - return types.any - end - local Parameters = Event:parameters() - local Self = EventHandler(types.any) - if Parameters.head then - table.insert(Parameters.head, 1, Self) - else - Parameters.head = {Self} - end - local SelfName = types.singleton("self") - if Parameters.names then - table.insert(Parameters.names, 1, SelfName) - else - Parameters.names = {SelfName} - end - return types.newfunction(Parameters, Event:returns(), Event:generics()) -end - -export type EventHandler ()> = { - -- .. snip .. - read fire: Fire, - -- .. snip .. -} - -local my_event: EventHandler<(my_message: string) -> ()> - -my_event:fire("Hello, world!") -- function EventHandler:fire(self: { read fire: any }, my_message: string): () -``` +Being able to add names to parameters defined in user-defined type functions increases the expressiveness of the type system, which is the primary motivation of the original [user-defined type functions RFC](https://github.com/luau-lang/rfcs/pull/45). ## Design @@ -50,53 +18,44 @@ The following changes need to be made to the user-defined type functions API: ### `types.newfunction` -The `parameters` argument gains an optional `names` field, which acts as a map from parameter position (in the `head` array) to a string singleton type, or `nil`. +A parameter may be either a simple type (with no parameter name, like in the current behavior), or a table that contains both a parameter name and a parameter type. -- A string singleton type is used instead of just a string for consistency with the table key API and to support future extensions (for example, doc comments). -- Names that are `nil`, not string singleton types, or invalid Luau identifiers should be ignored. +The `head` field of the `parameters` parameter is now of type `{type | Parameter}?`, where a `Parameter` is defined like this: ```luau -function types.newfunction( - parameters: { - head: {type}?, - names: { [number]: type }?, -- Doesn't need to be a contiguous array - tail: type? - }, - returns: { head: {type}?, tail: type? }, -- Return values don't have names (yet? 🤫) - generics: {type}? -) - -- .. snip .. -end +type Parameter = { name: string?, type: type } +``` + +Names that aren't strings, or that are invalid Luau identifiers, should be ignored. + +```luau +function types.newfunction(parameters: { head: {type | Parameter}?, tail: type? }, ...): type ``` ### `functiontype:setparameters` -This method gains an optional `names` argument, following the same rules as above. +The `head` parameter is now of type `{type | Parameter}?`, following the same rules as above. ```luau -function functiontype:setparameters( - head: {type}?, - tail: type?, - names: { [number]: type }?, -) -> () - -- .. snip .. -end +function functiontype:setparameters(head: {type | Parameter}?, tail: type?): () ``` ### `functiontype:parameters` -The returned table gains an optional `names` field, following the same rules as above. Furthermore, missing/invalid parameter names are always `nil` (and not some other representation, such as string singleton types with string length 0). +The `head` returned by this method is now of type `{Parameter}?`, there is no `type` union variant because the table is normalized, such that parameter names are always represented by a `Parameter` (unnamed parameters are *always* `{ name: nil, type: type }`, which avoids a branch). + +This change breaks backwards compatibility, because current type functions assume that `functiontype:parameters().head` is a list of types. See the [Alternatives](#alternatives) section. ```luau -function functiontype:parameters(): { - head: {type}?, - names: { [number]: type }?, - tail: type? -}, - -- .. snip .. -end +function functiontype:parameters(): { head: {Parameter}?, tail: type? } ``` ## Drawbacks -It's unclear if there are any drawbacks to this. Everything should be backwards compatible, and the motivation seems to align well enough with the goals of the language. +This proposal breaks backwards compatibility for the `functiontype:parameters()` method. + +## Alternatives + +Not doing this means users will continue to have problems where potentially important semantic information described in argument names will be lost using user-defined type functions. Furthermore, types that **need** to be created using user-defined type functions will not have parameter names, which makes creating self-describable functions harder. + +The `functiontype:parameters()` method **can** be done in a backwards-compatible way (for example, by using SoA instead of AoS), but it generally results in code that is difficult to understand and easy to get wrong. This RFC proposes that breaking backwards compatibility is a drawback that is acceptable in this case, especially since user-defined type functions are relatively recent. \ No newline at end of file From 0bf56b5b4241ff3412c7733578aa7e0abdb4ed73 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Sat, 11 Oct 2025 20:55:31 -0300 Subject: [PATCH 3/4] Change parameter type name to lowercase `parameter` --- docs/udtf-function-parameter-names.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/udtf-function-parameter-names.md b/docs/udtf-function-parameter-names.md index c8eba97f6..768fbabd0 100644 --- a/docs/udtf-function-parameter-names.md +++ b/docs/udtf-function-parameter-names.md @@ -1,4 +1,4 @@ -# Function Parameter Names in User-Defined Type Functions +# Function parameter Names in User-Defined Type Functions ## Summary @@ -20,34 +20,34 @@ The following changes need to be made to the user-defined type functions API: A parameter may be either a simple type (with no parameter name, like in the current behavior), or a table that contains both a parameter name and a parameter type. -The `head` field of the `parameters` parameter is now of type `{type | Parameter}?`, where a `Parameter` is defined like this: +The `head` field of the `parameters` parameter is now of type `{type | parameter}?`, where a `parameter` is defined like this: ```luau -type Parameter = { name: string?, type: type } +type parameter = { name: string?, type: type } ``` Names that aren't strings, or that are invalid Luau identifiers, should be ignored. ```luau -function types.newfunction(parameters: { head: {type | Parameter}?, tail: type? }, ...): type +function types.newfunction(parameters: { head: {type | parameter}?, tail: type? }, ...): type ``` ### `functiontype:setparameters` -The `head` parameter is now of type `{type | Parameter}?`, following the same rules as above. +The `head` parameter is now of type `{type | parameter}?`, following the same rules as above. ```luau -function functiontype:setparameters(head: {type | Parameter}?, tail: type?): () +function functiontype:setparameters(head: {type | parameter}?, tail: type?): () ``` ### `functiontype:parameters` -The `head` returned by this method is now of type `{Parameter}?`, there is no `type` union variant because the table is normalized, such that parameter names are always represented by a `Parameter` (unnamed parameters are *always* `{ name: nil, type: type }`, which avoids a branch). +The `head` returned by this method is now of type `{parameter}?`, there is no `type` union variant because the table is normalized, such that parameter names are always represented by a `parameter` (unnamed parameters are *always* `{ name: nil, type: type }`, which avoids a branch). This change breaks backwards compatibility, because current type functions assume that `functiontype:parameters().head` is a list of types. See the [Alternatives](#alternatives) section. ```luau -function functiontype:parameters(): { head: {Parameter}?, tail: type? } +function functiontype:parameters(): { head: {parameter}?, tail: type? } ``` ## Drawbacks @@ -58,4 +58,4 @@ This proposal breaks backwards compatibility for the `functiontype:parameters()` Not doing this means users will continue to have problems where potentially important semantic information described in argument names will be lost using user-defined type functions. Furthermore, types that **need** to be created using user-defined type functions will not have parameter names, which makes creating self-describable functions harder. -The `functiontype:parameters()` method **can** be done in a backwards-compatible way (for example, by using SoA instead of AoS), but it generally results in code that is difficult to understand and easy to get wrong. This RFC proposes that breaking backwards compatibility is a drawback that is acceptable in this case, especially since user-defined type functions are relatively recent. \ No newline at end of file +The `functiontype:parameters()` method **can** be done in a backwards-compatible way (for example, by using SoA instead of AoS), but it generally results in code that is difficult to understand and easy to get wrong. This RFC proposes that breaking backwards compatibility is a drawback that is acceptable in this case, especially since user-defined type functions are relatively recent. From 6b4fdfee509865a15fcb44c0ae003df4d934a4d8 Mon Sep 17 00:00:00 2001 From: Math <175355178+itsmath@users.noreply.github.com> Date: Sun, 12 Oct 2025 02:03:35 -0300 Subject: [PATCH 4/4] Fix title --- docs/udtf-function-parameter-names.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/udtf-function-parameter-names.md b/docs/udtf-function-parameter-names.md index 768fbabd0..ebcf1feed 100644 --- a/docs/udtf-function-parameter-names.md +++ b/docs/udtf-function-parameter-names.md @@ -1,4 +1,4 @@ -# Function parameter Names in User-Defined Type Functions +# Function Parameter Names in User-Defined Type Functions ## Summary