From de0bc30c1a4310c062eed89ee6bd0435c9640052 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:14:21 -0500 Subject: [PATCH 01/20] Create generic-constraints.md --- docs/generic-constraints.md | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/generic-constraints.md diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md new file mode 100644 index 0000000..2d96a6f --- /dev/null +++ b/docs/generic-constraints.md @@ -0,0 +1,61 @@ +# Generic Constraints +## Summary +Introduce syntax to extend/constrain the type of a generic. +## Motivation +Luau currently does not provide a way in order to constrain the type of a generic without direct assertions. +```lua +local qux = { foo = 10, bar = "string" } + +local function getProperty( object: T, key: keyof ) + return object[key] +end + +local foo = getProperty( qux, "foo" ) +-- foo: number | string +local bar = getProperty( qux, "bar" ) +-- bar: number | string +``` +This is wrong! We would expect foo to be a number, with bar being a string. +In the following snippet, it is impossible to tell whether or not the key variable is the same as the one being used to index T in the callback. +```lua +local function callbackProperty( object: T, key: keyof, callback: (index>) -> () ) +... +``` +## Design +The design of this would take inspiration from TypeScript's `extends`, where instead of the additional keyword, we would use `&`. +```lua +local qux = { foo = 10, bar = "string" } + +local function getProperty>( object: T, key: K ) + return object[key] +end + +local foo = getProperty( qux, "foo" ) +-- foo: number +local bar = getProperty( qux, "bar" ) +-- bar: string +``` +This would correctly infer the type of the each key's value. +The following snippet would also thus be correctly inferred. +```lua +local qux = { foo = 10, bar = "string" } + +local function callbackProperty( object: T, key: K, callback: (index) -> () ) + callback( object[key] ) +end + +callbackProperty( object, "foo", function( foo ) + -- foo: number -- this is expected! +end) +``` +An alternative syntax could be the following, but does not satisfy current requirements about `:` only being used inside `()` and `{}`, and personally does not look as good. +```lua +local function getProperty>( object: T, key: K ) ... end +local function callbackProperty( object: T, key: K, callback: (index) -> () ) ... end +``` +## Drawbacks +- I am not familiar with the internals of the typechecker but this would further complicate type inference. +- Adding an extra use to `&` could make its usage more confusing to novices. +## Alternatives +- Don't do this; this would make it impossible for functions like above to be able to be inferred correctly. Just let people explicitly type their variables instead of inferring types. This makes code more verbose and would likely not allow for full optimization. +- Use overloaded functions as previously mentioned, but this wouldn't allow the usage of a generic with correct type inference and would require users to add a new overload for each key. From 5228e18210fbf1f83ee762fe2ba4575dc673f291 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:14:56 -0500 Subject: [PATCH 02/20] Update generic-constraints.md edited drawbacks --- docs/generic-constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 2d96a6f..0d1d821 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -54,7 +54,7 @@ local function getProperty>( object: T, key: K ) ... end local function callbackProperty( object: T, key: K, callback: (index) -> () ) ... end ``` ## Drawbacks -- I am not familiar with the internals of the typechecker but this would further complicate type inference. +- I am not personally familiar with the internals of the typechecker, but this has a chance to further complicate type inference. - Adding an extra use to `&` could make its usage more confusing to novices. ## Alternatives - Don't do this; this would make it impossible for functions like above to be able to be inferred correctly. Just let people explicitly type their variables instead of inferring types. This makes code more verbose and would likely not allow for full optimization. From ad231459cc31929d4a0fde0917d9a6e7eb979714 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:15:34 -0500 Subject: [PATCH 03/20] Update generic-constraints.md fixed grammar! --- docs/generic-constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 0d1d821..daad262 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -58,4 +58,4 @@ local function callbackProperty( object: T, key: K, callback: (i - Adding an extra use to `&` could make its usage more confusing to novices. ## Alternatives - Don't do this; this would make it impossible for functions like above to be able to be inferred correctly. Just let people explicitly type their variables instead of inferring types. This makes code more verbose and would likely not allow for full optimization. -- Use overloaded functions as previously mentioned, but this wouldn't allow the usage of a generic with correct type inference and would require users to add a new overload for each key. +- Use overloaded functions as previously mentioned, but this wouldn't allow the usage of a generic, would require users to add a new overload for each key. From 60032131e238885279169033d6bdcab3746106c5 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:16:27 -0500 Subject: [PATCH 04/20] Update generic-constraints.md fixed syntax --- docs/generic-constraints.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index daad262..0fb0262 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -40,7 +40,7 @@ The following snippet would also thus be correctly inferred. ```lua local qux = { foo = 10, bar = "string" } -local function callbackProperty( object: T, key: K, callback: (index) -> () ) +local function callbackProperty>( object: T, key: K, callback: (index) -> () ) callback( object[key] ) end @@ -51,7 +51,7 @@ end) An alternative syntax could be the following, but does not satisfy current requirements about `:` only being used inside `()` and `{}`, and personally does not look as good. ```lua local function getProperty>( object: T, key: K ) ... end -local function callbackProperty( object: T, key: K, callback: (index) -> () ) ... end +local function callbackProperty>( object: T, key: K, callback: (index) -> () ) ... end ``` ## Drawbacks - I am not personally familiar with the internals of the typechecker, but this has a chance to further complicate type inference. From e29d515a2782cdb793cfe53f650741c2476a6412 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:25:48 -0500 Subject: [PATCH 05/20] Update generic-constraints.md fixed a code error --- docs/generic-constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 0fb0262..14c0cc9 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -44,7 +44,7 @@ local function callbackProperty>( object: T, key: K, callback: ( callback( object[key] ) end -callbackProperty( object, "foo", function( foo ) +callbackProperty( qux, "foo", function( foo ) -- foo: number -- this is expected! end) ``` From dcddfa3dbe3f00467d945ed1caaca065bdf775d7 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:29:12 -0500 Subject: [PATCH 06/20] Update generic-constraints.md oops; clarified code + added extra periods to make sure that code does not get confused with var args --- docs/generic-constraints.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 14c0cc9..ab166ba 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -2,8 +2,8 @@ ## Summary Introduce syntax to extend/constrain the type of a generic. ## Motivation -Luau currently does not provide a way in order to constrain the type of a generic without direct assertions. -```lua +Luau currently does not provide a way in order to constrain the type of a generic without direct assertions, and even so, would not properly infer the right type. +```luau local qux = { foo = 10, bar = "string" } local function getProperty( object: T, key: keyof ) @@ -17,13 +17,13 @@ local bar = getProperty( qux, "bar" ) ``` This is wrong! We would expect foo to be a number, with bar being a string. In the following snippet, it is impossible to tell whether or not the key variable is the same as the one being used to index T in the callback. -```lua +```luau local function callbackProperty( object: T, key: keyof, callback: (index>) -> () ) -... +.... ``` ## Design The design of this would take inspiration from TypeScript's `extends`, where instead of the additional keyword, we would use `&`. -```lua +```luau local qux = { foo = 10, bar = "string" } local function getProperty>( object: T, key: K ) @@ -37,7 +37,7 @@ local bar = getProperty( qux, "bar" ) ``` This would correctly infer the type of the each key's value. The following snippet would also thus be correctly inferred. -```lua +```luau local qux = { foo = 10, bar = "string" } local function callbackProperty>( object: T, key: K, callback: (index) -> () ) @@ -49,9 +49,9 @@ callbackProperty( qux, "foo", function( foo ) end) ``` An alternative syntax could be the following, but does not satisfy current requirements about `:` only being used inside `()` and `{}`, and personally does not look as good. -```lua -local function getProperty>( object: T, key: K ) ... end -local function callbackProperty>( object: T, key: K, callback: (index) -> () ) ... end +```luau +local function getProperty>( object: T, key: K ) .... end +local function callbackProperty>( object: T, key: K, callback: (index) -> () ) .... end ``` ## Drawbacks - I am not personally familiar with the internals of the typechecker, but this has a chance to further complicate type inference. From d2e5c56e60aa55ba24c29efc902d310f50c71ad4 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 00:09:01 -0500 Subject: [PATCH 07/20] Update generic-constraints.md fixed grammatical errors --- docs/generic-constraints.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index ab166ba..e645081 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -2,7 +2,7 @@ ## Summary Introduce syntax to extend/constrain the type of a generic. ## Motivation -Luau currently does not provide a way in order to constrain the type of a generic without direct assertions, and even so, would not properly infer the right type. +Luau currently does not provide a way in order to constrain the type of a generic. ```luau local qux = { foo = 10, bar = "string" } @@ -15,12 +15,14 @@ local foo = getProperty( qux, "foo" ) local bar = getProperty( qux, "bar" ) -- bar: number | string ``` -This is wrong! We would expect foo to be a number, with bar being a string. -In the following snippet, it is impossible to tell whether or not the key variable is the same as the one being used to index T in the callback. +Type interference believes that either value could be a number or string as keyof is too broad. + ```luau local function callbackProperty( object: T, key: keyof, callback: (index>) -> () ) .... ``` +It is impossible to tell whether or not the key variable is the same as the one being used to index T in the callback, and thus Luau infers that it could be a number | string. + ## Design The design of this would take inspiration from TypeScript's `extends`, where instead of the additional keyword, we would use `&`. ```luau @@ -36,7 +38,6 @@ local bar = getProperty( qux, "bar" ) -- bar: string ``` This would correctly infer the type of the each key's value. -The following snippet would also thus be correctly inferred. ```luau local qux = { foo = 10, bar = "string" } @@ -48,7 +49,9 @@ callbackProperty( qux, "foo", function( foo ) -- foo: number -- this is expected! end) ``` -An alternative syntax could be the following, but does not satisfy current requirements about `:` only being used inside `()` and `{}`, and personally does not look as good. +This would also be correctly inferred as index would correctly result in the type of the field. + +An alternative syntax could use `:` instead of `&`. However, this would not match the current semantics of `:` and using `&` implies a union of types. ```luau local function getProperty>( object: T, key: K ) .... end local function callbackProperty>( object: T, key: K, callback: (index) -> () ) .... end @@ -58,4 +61,4 @@ local function callbackProperty>( object: T, key: K, callback: (i - Adding an extra use to `&` could make its usage more confusing to novices. ## Alternatives - Don't do this; this would make it impossible for functions like above to be able to be inferred correctly. Just let people explicitly type their variables instead of inferring types. This makes code more verbose and would likely not allow for full optimization. -- Use overloaded functions as previously mentioned, but this wouldn't allow the usage of a generic, would require users to add a new overload for each key. +- Use overloaded functions as previously mentioned, but this would not allow the usage of a generic, and would thus require users to add a new overload for each key. From 6c0285743595ccf45a4df43e830501ceed34043b Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 18:57:11 -0500 Subject: [PATCH 08/20] Update generic-constraints.md integrate @aatxe's feedback --- docs/generic-constraints.md | 112 +++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 32 deletions(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index e645081..95b0fad 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -1,12 +1,12 @@ # Generic Constraints ## Summary -Introduce syntax to extend/constrain the type of a generic. +Introduce syntax to constrain the type of a generic. ## Motivation Luau currently does not provide a way in order to constrain the type of a generic. ```luau local qux = { foo = 10, bar = "string" } -local function getProperty( object: T, key: keyof ) +local function getProperty( object: T, key: keyof ): index> return object[key] end @@ -15,50 +15,98 @@ local foo = getProperty( qux, "foo" ) local bar = getProperty( qux, "bar" ) -- bar: number | string ``` -Type interference believes that either value could be a number or string as keyof is too broad. - +Type interference believes that either value could be a number or string as `keyof` is a union type of either foo or bar. ```luau -local function callbackProperty( object: T, key: keyof, callback: (index>) -> () ) +local function callbackProperty( object: T, key: keyof, callback: (property: index>) -> () ) .... ``` -It is impossible to tell whether or not the key variable is the same as the one being used to index T in the callback, and thus Luau infers that it could be a number | string. +It is impossible to tell whether or not `key` is the same variable being used to index `T` in the callback, and thus Luau infers the type of `property` to be `number | string`. +```luau +local function callbackProperty( object: T, key: keyof, callback: (property: index) -> () ) +... +``` +This does not work in Luau. +> Ideally, this behaviour should work out of the box, but, could be solved with bounded polymorphism. ## Design -The design of this would take inspiration from TypeScript's `extends`, where instead of the additional keyword, we would use `&`. +### Syntax +There are a few options for the syntax of generic constraints. +1. Treat the generic parameter as a variable--annotate it as declaring a new variable. ```luau -local qux = { foo = 10, bar = "string" } - -local function getProperty>( object: T, key: K ) - return object[key] +local function getProperty>( object: T, key: K ): index + return object[ key ] end -local foo = getProperty( qux, "foo" ) --- foo: number -local bar = getProperty( qux, "bar" ) --- bar: string +type getProperty> = ( object: T, key: K ) -> index ``` -This would correctly infer the type of the each key's value. -```luau -local qux = { foo = 10, bar = "string" } +This would match the current expectation of `:` being used to type annotate a variable. This would be backwards compatible without performance implications. As a note, the ordering of `T` and `K` is arbitrary and can be switched if desired. -local function callbackProperty>( object: T, key: K, callback: (index) -> () ) - callback( object[key] ) +2. Use a `where` clause. +```luau +local function getProperty>( object: T, key: K ): index + + return object[ key ] end -callbackProperty( qux, "foo", function( foo ) - -- foo: number -- this is expected! -end) +type getProperty = ( object: T, key: K ) -> ( index ) where< K: keyof > ``` -This would also be correctly inferred as index would correctly result in the type of the field. +This would allow users to specify the constraints of the separately from the generic declaration itself. This would be reminiscent to users of C#. Imposing multiple constraints on different generics can be done by delimiting with `,`. This would be backwards compatible, without major performance implications, as it could just be a conditional keyword. This could be used in conjuction with option 1. +> This isn't nearly as elegant for smaller types compared to option 1, but would be incredibly powerful for generics with lots of constraints. This allows for neatly distributing the declaration along multiple lines. -An alternative syntax could use `:` instead of `&`. However, this would not match the current semantics of `:` and using `&` implies a union of types. -```luau -local function getProperty>( object: T, key: K ) .... end -local function callbackProperty>( object: T, key: K, callback: (index) -> () ) .... end +### Usage +#### Subtyping Constraints +This is the problem posed at the beginning of this RFC. +```lua +local function callbackProperty>( object: T, key: K, callback: (property: index) -> () ) + callback( object[ key ] ) +end ``` +`K` will be constrained to be `keyof`. This would allow `index` to infer the type properly. +#### Equality Constraints +```luau +-- Attempt 1 +local function sumSpecific( a: number | vector, b: number | vector ): number | vector + return a + b -- not okay! +end +-- Attempt 2 +local function sumSpecific( a: T, b: T ): T + return a + b -- not okay! not all types are addable. +end +``` +This is to be expected. Luau does not interpret `a` and `b` as the same type, but if we use a generic then Luau throws an error that not all types are addable. +```luau +-- Attempt 3 +local sumSpecific: ( ( number, number ) -> ( number ) ) & ( ( vector, vector ) -> ( vector ) ) = function( a, b ) + return a + b -- okay! +end +``` +This is fine, but is not concise whatsoever, and does not allow for the traditional function declaration syntax. +```luau +local function sumSpecific( a: T, b: T ): T + return a + b +end + +sumSpecific( vector.create( 42, 42, 42 ) + vector.create( 143, 143, 143 ) ) -- okay! +sumSpecific( 42, 143 ) -- okay! + +sumSpecific( vector.create( 42, 42, 42 ), 143 ) -- error! a and b do not match. +``` +In this example, `T` must either be a number or vector, and setting annotating variables `a` and `b` as `T` would solve this. This can also be done by overloading the function but is more verbose and less elegant than this solution. This cannot be done with Luau's `add` type function, as it would allow all 'addable' types, and could not guarantee the return type being the same as both inputs. + ## Drawbacks -- I am not personally familiar with the internals of the typechecker, but this has a chance to further complicate type inference. -- Adding an extra use to `&` could make its usage more confusing to novices. +- There are no inequality constraints nor not-subtyping constraints. +> This drawback is difficult justify in Luau's current state. The introduction of type negation could solve this, but is outside the scope of this RFC. If type negation were to be added, the proposed syntax would allow for both of these types of constraints. +- This would complicate Luau's grammar and add an extra keyword. +> Any addition would add further complexity to the language. Adding the `where` keyword would also introduce another conditional keyword to the language. +- The introduction of bounded polymorphism would greatly complicate the compiler. +> I am not personally familiar with the internals of the typechecker. + ## Alternatives -- Don't do this; this would make it impossible for functions like above to be able to be inferred correctly. Just let people explicitly type their variables instead of inferring types. This makes code more verbose and would likely not allow for full optimization. -- Use overloaded functions as previously mentioned, but this would not allow the usage of a generic, and would thus require users to add a new overload for each key. +1. Don't do this; this would make it impossible for functions like above to be able to be automatically inferred correctly. Just let people explicitly annotate their variables instead of inferring types. This makes code more verbose. +> This would disallow for code that specifically makes use of generics to automatically output a response. +2. Manually write verbose overloaded functions types. +> This is not ideal, and suffers the same pitfalls as alternative 1. + +## Future Work +- If user-defined type functions supported generics, it would have to be expanded to support type constraints. +> This has major backwards compatibility implications depending on how user-defined type functions handle generics in the future. This won't be a problem if bounded polymorphism is implemented before that. From 5c01bf5ce62844bc0839decd2d56354bffaf932e Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 18:58:17 -0500 Subject: [PATCH 09/20] Update generic-constraints.md fixed tabs --- docs/generic-constraints.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 95b0fad..f86c8fb 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -7,7 +7,7 @@ Luau currently does not provide a way in order to constrain the type of a generi local qux = { foo = 10, bar = "string" } local function getProperty( object: T, key: keyof ): index> - return object[key] + return object[key] end local foo = getProperty( qux, "foo" ) @@ -34,7 +34,7 @@ There are a few options for the syntax of generic constraints. 1. Treat the generic parameter as a variable--annotate it as declaring a new variable. ```luau local function getProperty>( object: T, key: K ): index - return object[ key ] + return object[ key ] end type getProperty> = ( object: T, key: K ) -> index @@ -44,8 +44,7 @@ This would match the current expectation of `:` being used to type annotate a va 2. Use a `where` clause. ```luau local function getProperty>( object: T, key: K ): index - - return object[ key ] + return object[ key ] end type getProperty = ( object: T, key: K ) -> ( index ) where< K: keyof > @@ -58,7 +57,7 @@ This would allow users to specify the constraints of the separately from the gen This is the problem posed at the beginning of this RFC. ```lua local function callbackProperty>( object: T, key: K, callback: (property: index) -> () ) - callback( object[ key ] ) + callback( object[ key ] ) end ``` `K` will be constrained to be `keyof`. This would allow `index` to infer the type properly. @@ -66,11 +65,11 @@ end ```luau -- Attempt 1 local function sumSpecific( a: number | vector, b: number | vector ): number | vector - return a + b -- not okay! + return a + b -- not okay! end -- Attempt 2 local function sumSpecific( a: T, b: T ): T - return a + b -- not okay! not all types are addable. + return a + b -- not okay! not all types are addable. end ``` This is to be expected. Luau does not interpret `a` and `b` as the same type, but if we use a generic then Luau throws an error that not all types are addable. @@ -83,7 +82,7 @@ end This is fine, but is not concise whatsoever, and does not allow for the traditional function declaration syntax. ```luau local function sumSpecific( a: T, b: T ): T - return a + b + return a + b end sumSpecific( vector.create( 42, 42, 42 ) + vector.create( 143, 143, 143 ) ) -- okay! From 7a110a7e96cf4d980ddc6c6e1b17c329b6216282 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 18:58:31 -0500 Subject: [PATCH 10/20] Update generic-constraints.md fixed tabs again oops From f4f806e79ccc26d19b8aaae93545cb48d0adee6d Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:03:23 -0500 Subject: [PATCH 11/20] Update generic-constraints.md changed wording --- docs/generic-constraints.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index f86c8fb..8921213 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -39,7 +39,7 @@ end type getProperty> = ( object: T, key: K ) -> index ``` -This would match the current expectation of `:` being used to type annotate a variable. This would be backwards compatible without performance implications. As a note, the ordering of `T` and `K` is arbitrary and can be switched if desired. +This would match the current expectation of `:` being used to type annotate a variable. This would be backwards compatible without major performance implications. As a note, the ordering of `T` and `K` is arbitrary and can be switched if desired. 2. Use a `where` clause. ```luau @@ -97,8 +97,6 @@ In this example, `T` must either be a number or vector, and setting annotating v > This drawback is difficult justify in Luau's current state. The introduction of type negation could solve this, but is outside the scope of this RFC. If type negation were to be added, the proposed syntax would allow for both of these types of constraints. - This would complicate Luau's grammar and add an extra keyword. > Any addition would add further complexity to the language. Adding the `where` keyword would also introduce another conditional keyword to the language. -- The introduction of bounded polymorphism would greatly complicate the compiler. -> I am not personally familiar with the internals of the typechecker. ## Alternatives 1. Don't do this; this would make it impossible for functions like above to be able to be automatically inferred correctly. Just let people explicitly annotate their variables instead of inferring types. This makes code more verbose. From 000a18b9050ca24c70a2b310b7f00d54f7d7dda9 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:05:01 -0500 Subject: [PATCH 12/20] Update generic-constraints.md --- docs/generic-constraints.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 8921213..42dcf83 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -94,16 +94,14 @@ In this example, `T` must either be a number or vector, and setting annotating v ## Drawbacks - There are no inequality constraints nor not-subtyping constraints. -> This drawback is difficult justify in Luau's current state. The introduction of type negation could solve this, but is outside the scope of this RFC. If type negation were to be added, the proposed syntax would allow for both of these types of constraints. + > This drawback is difficult justify in Luau's current state. The introduction of type negation could solve this, but is outside the scope of this RFC. If type negation were to be added, the proposed syntax would allow for both of these types of constraints. - This would complicate Luau's grammar and add an extra keyword. -> Any addition would add further complexity to the language. Adding the `where` keyword would also introduce another conditional keyword to the language. + > Any addition would add further complexity to the language. Adding the `where` keyword would also introduce another conditional keyword to the language. +- If user-defined type functions supported generics, it would have to be expanded to support type constraints. + > This has major backwards compatibility implications depending on how user-defined type functions handle generics in the future. This won't be a problem if bounded polymorphism is implemented before that. ## Alternatives 1. Don't do this; this would make it impossible for functions like above to be able to be automatically inferred correctly. Just let people explicitly annotate their variables instead of inferring types. This makes code more verbose. -> This would disallow for code that specifically makes use of generics to automatically output a response. + > This would disallow for code that specifically makes use of generics to automatically output a response. 2. Manually write verbose overloaded functions types. -> This is not ideal, and suffers the same pitfalls as alternative 1. - -## Future Work -- If user-defined type functions supported generics, it would have to be expanded to support type constraints. -> This has major backwards compatibility implications depending on how user-defined type functions handle generics in the future. This won't be a problem if bounded polymorphism is implemented before that. + > This is not ideal, and suffers the same pitfalls as alternative 1. From dd7650a44be3d1e5f893b9785ff79ad9e0ba3008 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:05:56 -0500 Subject: [PATCH 13/20] Update generic-constraints.md added extra . for clarity and consistency --- docs/generic-constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 42dcf83..4a98c07 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -23,7 +23,7 @@ local function callbackProperty( object: T, key: keyof, callback: (propert It is impossible to tell whether or not `key` is the same variable being used to index `T` in the callback, and thus Luau infers the type of `property` to be `number | string`. ```luau local function callbackProperty( object: T, key: keyof, callback: (property: index) -> () ) -... +.... ``` This does not work in Luau. > Ideally, this behaviour should work out of the box, but, could be solved with bounded polymorphism. From 8e20e26f6c3480303be527b3f5049b83ee5c7800 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:08:17 -0500 Subject: [PATCH 14/20] Update generic-constraints.md added more for alternative 2 --- docs/generic-constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 4a98c07..0425ab3 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -104,4 +104,4 @@ In this example, `T` must either be a number or vector, and setting annotating v 1. Don't do this; this would make it impossible for functions like above to be able to be automatically inferred correctly. Just let people explicitly annotate their variables instead of inferring types. This makes code more verbose. > This would disallow for code that specifically makes use of generics to automatically output a response. 2. Manually write verbose overloaded functions types. - > This is not ideal, and suffers the same pitfalls as alternative 1. + > This suffers the same pitfalls as alternative 1, and does not allow for code to be easily expandable. An example can be found as Attempt 3 of Equality Constraints. From 690f8b7676e07c608d4d2686eb6ab95134c3b92e Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:09:12 -0500 Subject: [PATCH 15/20] Update generic-constraints.md oops, fixed where clause for function type --- docs/generic-constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 0425ab3..eed9c37 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -47,7 +47,7 @@ local function getProperty>( object: T, key: K ): index = ( object: T, key: K ) -> ( index ) where< K: keyof > +type getProperty> = ( object: T, key: K ) -> ( index ) ``` This would allow users to specify the constraints of the separately from the generic declaration itself. This would be reminiscent to users of C#. Imposing multiple constraints on different generics can be done by delimiting with `,`. This would be backwards compatible, without major performance implications, as it could just be a conditional keyword. This could be used in conjuction with option 1. > This isn't nearly as elegant for smaller types compared to option 1, but would be incredibly powerful for generics with lots of constraints. This allows for neatly distributing the declaration along multiple lines. From 9d1ed30d925d9e6c6f64740d8efb449395e5f3b5 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:31:30 -0500 Subject: [PATCH 16/20] Update generic-constraints.md added extra alternative --- docs/generic-constraints.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index eed9c37..dc47b7f 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -95,13 +95,29 @@ In this example, `T` must either be a number or vector, and setting annotating v ## Drawbacks - There are no inequality constraints nor not-subtyping constraints. > This drawback is difficult justify in Luau's current state. The introduction of type negation could solve this, but is outside the scope of this RFC. If type negation were to be added, the proposed syntax would allow for both of these types of constraints. -- This would complicate Luau's grammar and add an extra keyword. +- This would complicate Luau's grammar and add an extra keyword, and would thus add an additional learning curve to the language. > Any addition would add further complexity to the language. Adding the `where` keyword would also introduce another conditional keyword to the language. - If user-defined type functions supported generics, it would have to be expanded to support type constraints. > This has major backwards compatibility implications depending on how user-defined type functions handle generics in the future. This won't be a problem if bounded polymorphism is implemented before that. ## Alternatives -1. Don't do this; this would make it impossible for functions like above to be able to be automatically inferred correctly. Just let people explicitly annotate their variables instead of inferring types. This makes code more verbose. - > This would disallow for code that specifically makes use of generics to automatically output a response. -2. Manually write verbose overloaded functions types. - > This suffers the same pitfalls as alternative 1, and does not allow for code to be easily expandable. An example can be found as Attempt 3 of Equality Constraints. +1. **Don't do this.** +This would make it impossible for functions like above to be able to be automatically inferred correctly. Just let people explicitly annotate +their variables instead of inferring types. This makes code more verbose. This would disallow for code that specifically makes use of generics to automatically output a response. +2. **Manually write verbose overloaded functions types.** +This suffers the same pitfalls as alternative 1, and does not allow for code to be easily expandable. An example can be found as Attempt 3 of Equality Constraints. +3. **Get Luau to automatically infer bounds.** +This is really desirable behaviour, but is definitely non-trivial. Allowing for generic constraints removes ambiguity that might occur for generics. Specifically in one of the first examples... +```luau +local function getProperty( object: T, key: K ): index + return object[key] +end +``` +This should be possible, but simply isn't. The type inference engine currently cannot bound `K` such that `K` is `keyof`. An example of where this does work is as follows. +```luau +local function add( a: T, b: K ) + return a + b +end +``` +As such, the engine successfully bounds both `T` and `K` such that it satisfies `add`. This alternative could work if the engine eventually becomes smart enough, but manually placing bounds would still have a role as per the usage section of this RFC. +4. From d767a9283f2d6fe6b37478d81005300358d1a238 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:31:54 -0500 Subject: [PATCH 17/20] Update generic-constraints.md fixed changes --- docs/generic-constraints.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index dc47b7f..5459b3f 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -120,4 +120,3 @@ local function add( a: T, b: K ) end ``` As such, the engine successfully bounds both `T` and `K` such that it satisfies `add`. This alternative could work if the engine eventually becomes smart enough, but manually placing bounds would still have a role as per the usage section of this RFC. -4. From a41e48b4dd80f01163fa8329303aec58d7be3baf Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:52:45 -0500 Subject: [PATCH 18/20] Update generic-constraints.md added section for custom bounds and constraints --- docs/generic-constraints.md | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 5459b3f..743f0a8 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -30,7 +30,7 @@ This does not work in Luau. ## Design ### Syntax -There are a few options for the syntax of generic constraints. +There are two new options for syntax that come with this generic constraints. 1. Treat the generic parameter as a variable--annotate it as declaring a new variable. ```luau local function getProperty>( object: T, key: K ): index @@ -49,8 +49,15 @@ end type getProperty> = ( object: T, key: K ) -> ( index ) ``` -This would allow users to specify the constraints of the separately from the generic declaration itself. This would be reminiscent to users of C#. Imposing multiple constraints on different generics can be done by delimiting with `,`. This would be backwards compatible, without major performance implications, as it could just be a conditional keyword. This could be used in conjuction with option 1. -> This isn't nearly as elegant for smaller types compared to option 1, but would be incredibly powerful for generics with lots of constraints. This allows for neatly distributing the declaration along multiple lines. +This would allow users to specify the constraints of the separately from the generic declaration itself. This would be reminiscent to users of C#. Imposing multiple constraints on different generics can be done by delimiting with `,`. This would be backwards compatible, without major performance implications, as it could just be a conditional keyword. + +The `where` clause syntax can be further expanded to pose custom bounds on generics. +```luau +local function add>( a: T, b: K ) + return a + b +end +``` +The `add` function would only accept `T` and `K` if `add` being the return type. ### Usage #### Subtyping Constraints @@ -62,6 +69,7 @@ end ``` `K` will be constrained to be `keyof`. This would allow `index` to infer the type properly. #### Equality Constraints +Suppose we wanted to make a function that could only add `number`s or `vector`s. ```luau -- Attempt 1 local function sumSpecific( a: number | vector, b: number | vector ): number | vector @@ -92,13 +100,25 @@ sumSpecific( vector.create( 42, 42, 42 ), 143 ) -- error! a and b do not match. ``` In this example, `T` must either be a number or vector, and setting annotating variables `a` and `b` as `T` would solve this. This can also be done by overloading the function but is more verbose and less elegant than this solution. This cannot be done with Luau's `add` type function, as it would allow all 'addable' types, and could not guarantee the return type being the same as both inputs. +#### Other Constraints +```luau +local function multiply>( a: T, b: K ) + return a + b +end + +multiply(10, vector.create(10, 10, 10)) -- okay! +multiply(10, 41) -- okay! +multiply(10, "hello!") -- error! add<10, "hello"> is not okay. +``` +The `multiply` function would be bounded by `T: number`, along with `mul`. This can be used to create any type of constraint if user-defined type functions exist in the future. + ## Drawbacks -- There are no inequality constraints nor not-subtyping constraints. +- There are no built-in inequality constraints nor not-subtyping constraints. > This drawback is difficult justify in Luau's current state. The introduction of type negation could solve this, but is outside the scope of this RFC. If type negation were to be added, the proposed syntax would allow for both of these types of constraints. - This would complicate Luau's grammar and add an extra keyword, and would thus add an additional learning curve to the language. > Any addition would add further complexity to the language. Adding the `where` keyword would also introduce another conditional keyword to the language. - If user-defined type functions supported generics, it would have to be expanded to support type constraints. - > This has major backwards compatibility implications depending on how user-defined type functions handle generics in the future. This won't be a problem if bounded polymorphism is implemented before that. + > This has major backwards compatibility implications depending on how user-defined type functions handle generics in the future. ## Alternatives 1. **Don't do this.** From 6b959dd236c6217fd3af90b6efe684c3e108471e Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 20:01:48 -0500 Subject: [PATCH 19/20] Update generic-constraints.md fixed wording regarding user-defined type functions --- docs/generic-constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 743f0a8..69e243b 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -110,7 +110,7 @@ multiply(10, vector.create(10, 10, 10)) -- okay! multiply(10, 41) -- okay! multiply(10, "hello!") -- error! add<10, "hello"> is not okay. ``` -The `multiply` function would be bounded by `T: number`, along with `mul`. This can be used to create any type of constraint if user-defined type functions exist in the future. +The `multiply` function would be bounded by `T: number`, along with `mul`. This can be used to create any type of constraint if user-defined type functions work with generics in the future. ## Drawbacks - There are no built-in inequality constraints nor not-subtyping constraints. From 031ebc42889cb96c8996b0e0a8d9acb7a3751434 Mon Sep 17 00:00:00 2001 From: Kiiyoko <73446312+Kiiyoko@users.noreply.github.com> Date: Fri, 13 Dec 2024 23:51:10 -0500 Subject: [PATCH 20/20] Update generic-constraints.md fixed wording regarding the snippet of code that doesn't work --- docs/generic-constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generic-constraints.md b/docs/generic-constraints.md index 69e243b..c523a80 100644 --- a/docs/generic-constraints.md +++ b/docs/generic-constraints.md @@ -26,7 +26,7 @@ local function callbackProperty( object: T, key: keyof, callback: (propert .... ``` This does not work in Luau. -> Ideally, this behaviour should work out of the box, but, could be solved with bounded polymorphism. +> Ideally, this behaviour should work out of the box, but, could be solved with updates to the type engine. ## Design ### Syntax