From 0285afbd73e1b6cc8f1c039088c5f41bccf0a275 Mon Sep 17 00:00:00 2001 From: T 'Filtered' C Date: Sat, 17 Dec 2022 01:14:30 +0000 Subject: [PATCH 1/6] RFC: Constrained Generics --- rfcs/constrained-generics.md | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 rfcs/constrained-generics.md diff --git a/rfcs/constrained-generics.md b/rfcs/constrained-generics.md new file mode 100644 index 00000000..30a93a1e --- /dev/null +++ b/rfcs/constrained-generics.md @@ -0,0 +1,40 @@ +# Constrained Generics + +## Summary + +Allows you to give a function generic a constrained type. + +## Motivation + +Right now, when you create a generic function, the generic paramter is assumed to be everything. This creates problems, especially with tables +```lua +local function SomethingWithAGenericTable(tab: T) + for _, v in t do --Cannot call non-function T (T is not a table) + + end +end +``` + +This constraining is mainly for concrete table types and functions, since types like `number`, `string`, and `boolean` are literal types. + +## Design + +The way to implement this is to have new syntax under the generic's brackets: ``. This would constrain the type from the generic def and give the typechecker better hints. +```lua +local function SomethingWithAGenericTable(tab: T) + for _, v in t do + print(v) + end +end + +SomethingWithAGenericTable({meow = 5}) --OK +SomethingWithAGenericTable(5) --not OK +``` + +## Drawbacks + +This adds unusual syntax, as well as adding to the complexity of the language. + +## Alternatives + +Do nothing, and use the `assert(type(T) == T)` syntax, though this creates extra bytecode. From 16970abbfb34baaab2ed8f9e7d0d57227503f116 Mon Sep 17 00:00:00 2001 From: T 'Filtered' C Date: Sat, 17 Dec 2022 01:16:28 +0000 Subject: [PATCH 2/6] type pack drawback --- rfcs/constrained-generics.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfcs/constrained-generics.md b/rfcs/constrained-generics.md index 30a93a1e..db7493e0 100644 --- a/rfcs/constrained-generics.md +++ b/rfcs/constrained-generics.md @@ -35,6 +35,8 @@ SomethingWithAGenericTable(5) --not OK This adds unusual syntax, as well as adding to the complexity of the language. +This also doesn't address how this should be approached around type packs. + ## Alternatives Do nothing, and use the `assert(type(T) == T)` syntax, though this creates extra bytecode. From 353e69324b7006e5d487814aaa91f11621256dbd Mon Sep 17 00:00:00 2001 From: T 'Filtered' C Date: Fri, 13 Jan 2023 01:59:26 +0000 Subject: [PATCH 3/6] Update constrained-generics.md --- rfcs/constrained-generics.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rfcs/constrained-generics.md b/rfcs/constrained-generics.md index db7493e0..e47d542f 100644 --- a/rfcs/constrained-generics.md +++ b/rfcs/constrained-generics.md @@ -31,6 +31,8 @@ SomethingWithAGenericTable({meow = 5}) --OK SomethingWithAGenericTable(5) --not OK ``` +> TODO: Partially constrained generics, where part of the type is known but can be expaned upon, similar to TypeScript's `extends` keyword. + ## Drawbacks This adds unusual syntax, as well as adding to the complexity of the language. @@ -39,4 +41,9 @@ This also doesn't address how this should be approached around type packs. ## Alternatives -Do nothing, and use the `assert(type(T) == T)` syntax, though this creates extra bytecode. +Use something similar to Rust's `where` syntax: +```lua +local function SomethingWithAGenericTable(tab: T) where T: {[string]: any} +``` + +Do nothing, and use the `assert(type(T) == T)` syntax, though this creates extra bytecode. It also casts the object as `never`, which removes the type errors, however no autocomplete information is generated. From 2dcc928c398f189fa6a790fd00cf5dde4ebecd23 Mon Sep 17 00:00:00 2001 From: T 'Filtered' C Date: Sat, 21 Jan 2023 07:37:41 +0000 Subject: [PATCH 4/6] Remove TODO note Probably better if we just support {a: b, [c]: d} --- rfcs/constrained-generics.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/rfcs/constrained-generics.md b/rfcs/constrained-generics.md index e47d542f..ca0438b6 100644 --- a/rfcs/constrained-generics.md +++ b/rfcs/constrained-generics.md @@ -31,8 +31,6 @@ SomethingWithAGenericTable({meow = 5}) --OK SomethingWithAGenericTable(5) --not OK ``` -> TODO: Partially constrained generics, where part of the type is known but can be expaned upon, similar to TypeScript's `extends` keyword. - ## Drawbacks This adds unusual syntax, as well as adding to the complexity of the language. From 9a4225210f3bcb0ad7c28df9cd888f1c5f65235b Mon Sep 17 00:00:00 2001 From: T 'Filtered' C Date: Mon, 23 Jan 2023 22:39:05 +0000 Subject: [PATCH 5/6] Update constrained-generics.md --- rfcs/constrained-generics.md | 51 ++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/rfcs/constrained-generics.md b/rfcs/constrained-generics.md index ca0438b6..f3ee82d3 100644 --- a/rfcs/constrained-generics.md +++ b/rfcs/constrained-generics.md @@ -6,33 +6,56 @@ Allows you to give a function generic a constrained type. ## Motivation -Right now, when you create a generic function, the generic paramter is assumed to be everything. This creates problems, especially with tables -```lua -local function SomethingWithAGenericTable(tab: T) - for _, v in t do --Cannot call non-function T (T is not a table) +Some functions, such as the one below, allow you to feed it a table, and the table's keys are fed into the base object. With the current implementation of generics, this cannot be done and as such, the autocomplete information of the object is unavailable - end -end +```lua +type Enum = { + GetEnumItems: (Enum) -> {[string]: EnumItem} +} & T + +local function Enum(description: T): Enum + local enum = {} + + -- merge keys into the enum + for _, enumItem in description do -- Cannot call non-function T + table.insert(enum, enumItem) + end ``` +Without the generics, this would type would become + +```lua +{GetEnumItems(Enum) -> {[string]: number}, items: {[string[: number}} +``` + +In reality, we'd prefer it if items and GetEnumItems return `T`, instead of explicitly declared dictionaries. + This constraining is mainly for concrete table types and functions, since types like `number`, `string`, and `boolean` are literal types. ## Design The way to implement this is to have new syntax under the generic's brackets: ``. This would constrain the type from the generic def and give the typechecker better hints. ```lua -local function SomethingWithAGenericTable(tab: T) - for _, v in t do - print(v) - end -end +local function Enum(description: T): Enum + local enum = {} -SomethingWithAGenericTable({meow = 5}) --OK -SomethingWithAGenericTable(5) --not OK + -- merge keys into the enum + for _, enumItem in description do -- Typechecker now knows T is at least a string table, so this becomes somewhat valid + table.insert(enum, enumItem) + end +``` + +This will also help the typechecker from the outside looking into the function, for example: +```lua +Enum {None = 0, Some = 1, All = 2} --OK +Enum {"None", "Some", "All"} -- Not OK (generic must be have string keys) +Enum(0) -- Not ok (generic must be a table) ``` ## Drawbacks +More of the complexity budget spent. + This adds unusual syntax, as well as adding to the complexity of the language. This also doesn't address how this should be approached around type packs. @@ -41,7 +64,7 @@ This also doesn't address how this should be approached around type packs. Use something similar to Rust's `where` syntax: ```lua -local function SomethingWithAGenericTable(tab: T) where T: {[string]: any} +local function Enum(tab: T) where T: {[string]: any} ``` Do nothing, and use the `assert(type(T) == T)` syntax, though this creates extra bytecode. It also casts the object as `never`, which removes the type errors, however no autocomplete information is generated. From 49822e765c173c6aaddaf979db17760e94757f0a Mon Sep 17 00:00:00 2001 From: T 'Filtered' C Date: Mon, 23 Jan 2023 22:41:31 +0000 Subject: [PATCH 6/6] Update constrained-generics.md --- rfcs/constrained-generics.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rfcs/constrained-generics.md b/rfcs/constrained-generics.md index f3ee82d3..2d075329 100644 --- a/rfcs/constrained-generics.md +++ b/rfcs/constrained-generics.md @@ -52,6 +52,14 @@ Enum {"None", "Some", "All"} -- Not OK (generic must be have string keys) Enum(0) -- Not ok (generic must be a table) ``` +Finally, this will also give the autocomplete the correct information about the Enum, correctly containing the "None", "Some" and "All" keys +```lua +Enum. --> GetEnumItems: (Enum) -> {None: number, Some: number, All: number} + --> None: number + --> Some: number + --> All: number +``` + ## Drawbacks More of the complexity budget spent.