├── threading.md ├── random.md ├── generics.md ├── specnotes.md ├── typerecords.md ├── decorators.md └── README.md /threading.md: -------------------------------------------------------------------------------- 1 | # Threading Extension and Notes 2 | 3 | ECMAScript needs real threading where any function can be spawned as a thread to run asynchronously. Most value type data should be accessible across threads with some operations being atomic automatically. The syntax for creating and managing threads should be very minimal and effortless to use. 4 | 5 | For example, you should be able to define a global ```a: uint32``` and in a thread ```Atomics.add(a, 5)``` it without shuffling it into a typed array. 6 | 7 | ```js 8 | let a: uint32 = 0; 9 | function A() { 10 | Atomics.add(a, 5); 11 | } 12 | async function B() { 13 | A(); 14 | Atomics.add(a, 5); 15 | } 16 | // Naive call syntax to show these execute on their own thread and callThread returns a promise. 17 | await Promise.all([A.callThread(), B.callThread()]); // Join 18 | a; // 15 19 | ``` 20 | 21 | The ```callThread``` method would return a Promise. Internally it spawns a thread that automatically closes over all the state referenced. In fully typed code this operation can be relatively optimizable; however, it is possible to use this with dynamic code with higher costs, but this usage would be rare. 22 | 23 | It was my hope that a Cancelable Promise proposal would have been finalized by now. Assume one exists. 24 | 25 | Some value type operations would be atomic automatically. Addition on integers for instance. 26 | ```js 27 | let a: uint32 = 0; 28 | function A() { 29 | while (true) { 30 | a += 5; 31 | } 32 | } 33 | A.callThread(); 34 | await new Promise(resolve => setTimeout(resolve, 100)); 35 | a; // 36 | ``` 37 | 38 | ## Applications 39 | 40 | * Game algorithms like pathfinding 41 | * Parsing large binary data formats when using binary Web Sockets. 42 | 43 | ## Future Applications 44 | 45 | * Building DOM nodes in a separate thread then appending in the main thread. This is intuitive for programmers, but currently is not possible. In an ideal web environment this would just work where you could document.createElement in a function and as long as you didn't try to reference the active DOM you'd be fine. 46 | * In a large single page application multiple threads could be spun up creating different sections of the DOM that are then joined and appended to the document. 47 | * In cases where you're waiting for data from a REST call and get a JSON object back you then need to process the data. A thread could do the rest call, perform the JSON.parse, processing, then return back the data without having to postMessage. 48 | 49 | ## Concurrent Data Structures 50 | 51 | Concurrent data structures would be nice to have with native implementations. 52 | 53 | ## Pipelines 54 | 55 | WIP How do pipelines fit into this? Intuitively piping data to a threaded function should just work and create a thread. Is that a realistic scenario though? 56 | 57 | # Node.JS 58 | 59 | It should be assumed that Node.JS would use this as well where offloading parsing and expensive operations to threads is very beneficial. 60 | -------------------------------------------------------------------------------- /random.md: -------------------------------------------------------------------------------- 1 | # Math.random Extension 2 | 3 | The goal of this extension is to describe how a random proposal would change with types. This will cover the basic generation tasks like random numbers and seeded randoms with various methods. 4 | 5 | Refer to this as I'll base these notes off of it: https://github.com/tc39/proposal-seeded-random 6 | 7 | **Note**: This is a pseudorandom library identical to Math.random and not cryptographically random. It's designed around speed for things like games. Extending the Crypto library later into ECMAScript would probably be ideal to have a wide range of Crypto RNG generation. 8 | 9 | ### Math.random.() 10 | 11 | The first addition is a generic version of ```Math.random``` for the float types: 12 | 13 | ```float16```, ```float32```, ```float64``` 14 | 15 | The second generic argument is a method set to the browser default PRNG method. Not sure how to namespace that. Like ```Math.PRNG.Default```. 16 | 17 | Rapidly generating arrays of random numbers in the range \[0, 1): 18 | 19 | ```js 20 | const a: [100].; 21 | Math.random.(a); 22 | ``` 23 | 24 | When used with an integer type it will generate a random integer in their range. 25 | 26 | ```int8```, ```int16```, ```int32```, ```int64``` 27 | ```uint8```, ```uint16```, ```uint32```, ```uint64``` 28 | 29 | ```js 30 | const a = Math.random.(); 31 | a; // 0 to 255 32 | ``` 33 | 34 | Generating a random RGB color: 35 | ```js 36 | const a: [3].; 37 | Math.random.(a); 38 | a; // [100, 20, 25] as an example 39 | ``` 40 | 41 | ### Math.random.(min: T, max: T) 42 | 43 | For generating between a min and max inclusive the following data types are allowed: 44 | 45 | ```int8```, ```int16```, ```int32```, ```int64``` 46 | ```uint8```, ```uint16```, ```uint32```, ```uint64``` 47 | ```bigint``` 48 | ```float16```, ```float32```, ```float64``` 49 | 50 | So to generate an int32 between -5 and 5 you only need to do: 51 | 52 | ```js 53 | Math.random.(-5, 5); 54 | ``` 55 | 56 | Rapidly generating arrays of random numbers in the range \[-1, 1]: 57 | 58 | ```js 59 | const prng = Math.seededRandom.({ seed: 0 }); 60 | const a: [100].; 61 | prng.random(a, -1, 1); 62 | ``` 63 | 64 | Could also define ```Math.random(max)``` since function overloading exists. 65 | 66 | ### Math.seededRandom.(config) 67 | 68 | ```js 69 | const prng = Math.seededRandom.({ seed: 0 }); 70 | const i = prng.random(); // [0, 1) 71 | const j = prng.random(-5, 5); // [-5, 5] 72 | ``` 73 | 74 | Rapidly generating arrays of random numbers: 75 | 76 | ```js 77 | const prng = Math.seededRandom.({ seed: 0 }); 78 | const a: [100].; 79 | prng.random(a, -1, 1); 80 | ``` 81 | 82 | ### Web Workers 83 | 84 | Sharing seeded random across Web Workers isn't allowed. Using an atomic index into an array would work. Or each thread just creates its own seeded random and uses that. 85 | 86 | ### WIP 87 | 88 | Extracting state to save is still up for debate. I'd think a standardized ```[].``` array of binary data would be sufficient for each PRNG method. Basically just need a way that works as nobody is inspecting it probably. 89 | -------------------------------------------------------------------------------- /generics.md: -------------------------------------------------------------------------------- 1 | # Generics 2 | 3 | The goal of generics would be represent compile-time or at the least JIT optimized codepaths. In this way they're more similar to C++ templates. In a type system they allow simple generic classes for specializing for types. 4 | 5 | The big pictue of this section is to write out a near complete generics section to ensure types aren't implemented in a way that makes this awkward. It should be near seamless to introduce these as the main proposal relies on them in a few language feature areas. 6 | 7 | ```js 8 | class A { 9 | a:T; 10 | constructor(a:T) { 11 | this.a = a; 12 | } 13 | } 14 | const a = new A(5); 15 | const b = new A(1024); 16 | ``` 17 | 18 | In that example by default the field ```a``` is type ```uint8```, but the programmer foresaw someone might need to change this sometimes. Rather than hardcode this, the library exposes a generic parameter. 19 | 20 | ### Constraints 21 | 22 | Often not just any type can be passed into the generic argument. Nearly every language has a constraint system to specify what interface(s) a type must implement. 23 | 24 | ```js 25 | class A { 26 | } 27 | ``` 28 | Simple syntax, but often you want to apply multiple interface constraints. TypeScript uses ```&```. 29 | 30 | ```js 31 | class A { 32 | } 33 | ``` 34 | I think that's sufficient and covers common use cases. 35 | 36 | ### Value Type Generic Parameters 37 | 38 | A value can be passed into generics like a function argument. The only caveat is they must be const and will be treated like const variables that are compiled away. 39 | 40 | ```js 41 | class A { 42 | f():int32 { 43 | return 2**V; 44 | } 45 | } 46 | const a = new A<5>(); 47 | a.f(); 48 | ``` 49 | 50 | In this examples 5 is passed into the class creating essentially a unique implementation. For all purposes the first pass of the JIT would see it something like this: 51 | 52 | ```js 53 | class A5 e { 54 | f():int32 { 55 | return 32; 56 | } 57 | } 58 | const a = new A5(); 59 | a.f(); 60 | ``` 61 | 62 | #### Passing generic value type arguments 63 | 64 | Since the goal is optimization it is impossible to pass a non-const to a generic value type. This is fine as long as the right expression is also completely const: 65 | 66 | ```js 67 | function f() { 68 | const v = 5; 69 | const a = new A(); 70 | const b = new A(); 71 | } 72 | ``` 73 | 74 | If this value needs to be defined outside of our function we can make it generic: 75 | ```js 76 | function f() { 77 | const a = new A(); 78 | const b = new A(); 79 | } 80 | f<5>(); 81 | ``` 82 | This preserves the requirement that the argument needs to be const. 83 | 84 | #### Syntax to get the value type's type 85 | 86 | To increase readability often you want to define the type once and refer to the parameter's type. Something like ```decltype```, but a better keyword. Or is that good? 87 | ```js 88 | class A { 89 | f():type(V) { 90 | } 91 | } 92 | ``` 93 | 94 | ### Using Value Type Classes as parameters 95 | 96 | WIP, is this necessary? Is it possible? What does it allow? They are non-dynamic, just like ```int32```, so they should be alright. 97 | 98 | ### Decorator Generics 99 | 100 | WIP Generic decorators should work as expected. Write a few examples. 101 | -------------------------------------------------------------------------------- /specnotes.md: -------------------------------------------------------------------------------- 1 | # Required Changes to the Specification: 2 | 3 | ### 6.1 ECMAScript Language Types 4 | 5 | Would need to include the types listed above. Probably in a more verbose view than a list. 6 | 7 | ### 6.1.3 The Boolean Type 8 | 9 | ```boolean``` is an alias for ```Boolean``` when ```boolean``` is imported. 10 | 11 | ### 6.1.4 The String Type 12 | 13 | ```string``` is an alias for ```String``` when ```string``` is imported. 14 | 15 | ### 6.1.5 The Symbol Type 16 | 17 | ```symbol``` is an alias for ```Symbol``` when ```symbol``` is imported. 18 | 19 | ### 6.1.6 The Number Type 20 | 21 | ```number``` is an alias for ```Number``` when ```number``` is imported. 22 | 23 | ### 6.1.7 The Object Type 24 | 25 | ```object``` is an alias for ```Object``` when ```object``` is imported. 26 | 27 | ### 6.1.8 Integral Types 28 | 29 | #### 6.1.8.1 Signed 30 | 31 | ```int8```, ```int16```, ```int32```, ```int64``` 32 | 33 | ```int8.parse(string, radix = 10)``` 34 | 35 | #### 6.1.8.2 Unsigned 36 | 37 | ```uint8```, ```uint16```, ```uint32```, ```uint64``` 38 | 39 | ```uint8.parse(string, radix = 10)``` 40 | 41 | ### 6.1.9 Big Integer 42 | 43 | ```bigint``` 44 | 45 | ```bigint.parse(string, radix = 10)``` 46 | 47 | ### 6.1.10 Float 48 | 49 | ```float16```, ```float32```, ```float64```, ```float80```, ```float128``` 50 | 51 | TODO: Requirements in the spec? Is referring to the specs for each sufficient? 52 | 53 | https://en.wikipedia.org/wiki/Half-precision_floating-point_format 54 | https://en.wikipedia.org/wiki/Single-precision_floating-point_format 55 | https://en.wikipedia.org/wiki/Double-precision_floating-point_format 56 | https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format 57 | 58 | ### 6.1.11 Decimal 59 | 60 | ```decimal32```, ```decimal64```, ```decimal128``` 61 | 62 | https://en.wikipedia.org/wiki/Decimal32_floating-point_format 63 | https://en.wikipedia.org/wiki/Decimal64_floating-point_format 64 | https://en.wikipedia.org/wiki/Decimal128_floating-point_format 65 | 66 | ### 6.1.12 SIMD 67 | 68 | ```boolean8x16```, ```boolean16x8```, ```boolean32x4```, ```boolean64x2```, ```boolean8x32```, ```boolean16x16```, ```boolean32x8```, ```boolean64x4``` 69 | ```int8x16```, ```int16x8```, ```int32x4```, ```int64x2```, ```int8x32```, ```int16x16```, ```int32x8```, ```int64x4``` 70 | ```uint8x16```, ```uint16x8```, ```uint32x4```, ```uint64x2```, ```uint8x32```, ```uint16x16```, ```uint32x8```, ```uint64x4``` 71 | ```float32x4```, ```float64x2```, ```float32x8```, ```float64x4``` 72 | 73 | ### 6.1.13 Rational 74 | 75 | ```rational``` 76 | 77 | https://en.wikipedia.org/wiki/Rational_data_type 78 | 79 | ### 6.1.14 Complex 80 | 81 | ```complex``` 82 | 83 | https://en.wikipedia.org/wiki/Complex_data_type 84 | 85 | ### 6.1.15 Any 86 | 87 | ```any``` 88 | 89 | ### 6.1.16 Void 90 | 91 | ```void``` 92 | 93 | Used solely in function signatures to denote that a return value does not exist. 94 | 95 | 96 | ### 7.2.12 SameValueNonNumber ( x, y ) 97 | 98 | TODO:... 99 | 100 | The internal comparison abstract operation SameValueNonNumber(x, y), where neither x nor y are Number values, produces true or false. Such a comparison is performed as follows: 101 | 102 | Assert: Type(x) is not Number. 103 | Assert: Type(x) is the same as Type(y). 104 | If Type(x) is Undefined, return true. 105 | If Type(x) is Null, return true. 106 | If Type(x) is String, then 107 | If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices), return true; otherwise, return false. 108 | If Type(x) is Boolean, then 109 | If x and y are both true or both false, return true; otherwise, return false. 110 | If Type(x) is Symbol, then 111 | If x and y are both the same Symbol value, return true; otherwise, return false. 112 | If x and y are the same Object value, return true. Otherwise, return false. 113 | 114 | 115 | 116 | ### 7.2.14 Abstract Equality Comparison 117 | 118 | The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows: 119 | 120 | 1. If Type(x) is the same as Type(y), then 121 | a. Return the result of performing Strict Equality Comparison x === y. 122 | 2. If Type(x) has an implicit cast to Type(y), then 123 | a. 124 | 3. 125 | 2. If x is null and y is undefined, return true. 126 | 3. If x is undefined and y is null, return true. 127 | 4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y). 128 | 5. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y. 129 | 6. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. 130 | 7. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). 131 | 8. If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y). 132 | 9. If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y. 133 | 10. Return false. 134 | 135 | 136 | 137 | ### 11.6.2.1 138 | 139 | Move enum from 11.6.2.2 to 11.6.2.1. 140 | 141 | ### 11.8.3.1 Static Semantics: MV 142 | 143 | This needs to be changed to work with more than Number. Ideally this operation is delayed until the type is determined. As an example the following should be intuitively legal without any Number conversion done. 144 | 145 | ```js 146 | let a:uint64 = 0xffffffffffffffff; 147 | ``` 148 | 149 | The same could be true for bigint support. 150 | 151 | ### 12.5.6 The typeof Operator 152 | 153 | The table needs to be updated with all the new types and nullable type explanation. 154 | 155 | ### 12.10 Relational Operators 156 | 157 | 158 | ### 12.11 Equality Operators 159 | 160 | Not sure if something needs to be changed in these. 161 | 162 | ### 12.5.7 to 12.11 163 | 164 | Theese contain the operator definitions. Would probably need to include at least a brief change to explain the behavior of all the types. SIMD ones would require the most explanation. 165 | 166 | ### 12.14.5 Destructuring Assignment 167 | 168 | Type casting syntax described above would need to be included. 169 | 170 | ### 13.3.2 Variable Statement 171 | 172 | Would need to cover the optional typing syntax and grammar. 173 | 174 | ### 13.15 The try Statement 175 | 176 | CatchParameter needs to be modified to allow type constraints. 177 | 178 | ### 14 ECMAScript Language: Functions and Classes 179 | 180 | Function overloading would need to be included and the expected operations for matching a list of arguments to parameters and types. This will also need to cover cases like ambiguous function overloading. 181 | 182 | ### 14.2 Arrow Function Definitions 183 | 184 | The grammar rule ArrowFunction needs an optional return type and ArrowParameters needs optional type information per each parameter. 185 | 186 | ### 14.3 Method Definitions 187 | 188 | Grammar requires typing information as defined above. Specifically MethodDefinition's first rule and the get and set ones. 189 | 190 | ### 14.5 Class Definitions 191 | 192 | Grammar requires typing information for members and methods. Specifically ClassElement and MethodDefinition. ConstructorMethod is referenced as being a MethodDefinition so it should be fine after the MethodDefinition changes. 193 | 194 | ### 19.2 Function Objects 195 | 196 | Needs to support type information in the constructor. Examples from the documentation with typed examples: 197 | 198 | ```js 199 | new Function("a:string", "b:uint8", "c:int32", "return a + b + c;") 200 | new Function("a:string, b:uint8, c:int32", "return a + b + c;") 201 | new Function("a:string,b:uint8", "c:int32", "return a + b + c;") 202 | ``` 203 | 204 | Syntax to define a return type: 205 | 206 | ```js 207 | new Function("a:string", "b:uint8[]", "c:int32", ":string", "return a + b + c;") 208 | ``` 209 | 210 | ### New Sections for Each Type 211 | 212 | As described before each type needs a parse function to turn a string into the type. Also for float and decimal .EPSILON needs defined in their section. Also for all the integer (not bigint), float, and decimal types need MIN_VALUE and MAX_VALUE defined in their section. 213 | 214 | ### 20.2 The Math Object 215 | 216 | All the math operations need to be overloaded to work with the integer, float, and decimal types. Meaning if they take in the type they should return the same type. 217 | 218 | ### 25.2.2 Properties of the GeneratorFunction Constructor 219 | 220 | Similar to Function the constructor needs to be changed to allow types. For example: 221 | 222 | ```js 223 | new GeneratorFunction("a:float32", ":float32", "yield a * 2;"); 224 | ``` 225 | -------------------------------------------------------------------------------- /typerecords.md: -------------------------------------------------------------------------------- 1 | # Type Format 2 | 3 | An exposed type format is available to programmers. This can also be used internally in engines. 4 | 5 | ### Deterministic format 6 | 7 | TODO: Identical types encode to the same record. (Define this algorithm). Basically expanding all the references to types should create identical records between the same types independent of their order in unions and intersections. Sorting should be somewhat sufficient? 8 | 9 | ### Union 10 | 11 | ```js 12 | #{ 13 | union: #[] 14 | } 15 | ``` 16 | 17 | ### Intersection 18 | ```js 19 | #{ 20 | intersection: #[] 21 | ] 22 | ``` 23 | 24 | #### Literals 25 | 26 | Any literals, including Symbols, can be used in a type. 27 | 28 | ```js 29 | const T = type 'a' | 'b' | 'c'; 30 | 31 | #{ 32 | union: #[ 33 | 'a' 34 | 'b', 35 | 'c' 36 | ] 37 | } 38 | ``` 39 | 40 | ```js 41 | const T = type 0 | 1 | 1.5; 42 | 43 | #{ 44 | union: #[ 45 | 0, 46 | 1, 47 | 1.5 48 | ] 49 | } 50 | ``` 51 | 52 | Numerical literals have no inherent type, so an intersection can be used to constrain them: 53 | 54 | ```js 55 | const T = type float32 & (0 | 1 | 1.5); 56 | 57 | #{ 58 | intersection: #[ 59 | float32, 60 | #{ 61 | union: #[ 62 | 0, 63 | 1, 64 | 1.5 65 | ] 66 | } 67 | ] 68 | } 69 | ``` 70 | 71 | This also handles tagged unions: 72 | 73 | ```js 74 | const T = type 75 | | { kind: 'success', data: string } 76 | | { kind: 'error', message: string }; 77 | 78 | #{ 79 | union: #[ 80 | #{ 81 | properties: #[ 82 | #{ name: 'kind', type: 'success' }, 83 | #{ name: 'data', type: string } 84 | ] 85 | }, 86 | #{ 87 | properties: #[ 88 | #{ name: 'kind', type: 'error' }, 89 | #{ name: 'message', type: string } 90 | ] 91 | } 92 | ] 93 | } 94 | ``` 95 | 96 | ### function type 97 | 98 | Functions have a signature defined by a parameters record and a return type. 99 | 100 | ```js 101 | const T = type function(x: number): string { return x.toString(); } 102 | 103 | #{ 104 | parameters: #{ 105 | x: number 106 | }, 107 | return: string 108 | } 109 | ``` 110 | 111 | This use of a record means that these two functions have the same signature and the second would produce a TypeError: 112 | 113 | ```js 114 | function f(x: number, y: string): void {} 115 | // function f(y: string, x: number): void {} // TypeError: A function 'f' with that signature already exists. 116 | ``` 117 | 118 | When using named parameters ```f(x: 0, y: 'abc')``` such calls would have been ambiguous also. 119 | 120 | #### Optional parameter 121 | 122 | ```js 123 | function f(x?: boolean): void {} 124 | //function f(x: boolean = true): void {} // Identical signature 125 | 126 | const T = type f; 127 | 128 | #{ 129 | parameters: #{ 130 | x: #{ 131 | type: boolean, 132 | optional: true 133 | } 134 | }, 135 | return: void 136 | } 137 | ``` 138 | 139 | (Note: ```optional``` is used because expanding these to unique signatures would mean a function with 8 optional parameters would have 256 signatures). 140 | 141 | #### Overloaded functions 142 | 143 | Overloaded functions are interesting because their type record can be quite massive, especially generic functions and decorators. 144 | 145 | ```js 146 | function f(x: string): number {} 147 | function f(x: number): string {} 148 | function f(x: string, y: boolean): number {} 149 | 150 | const T = type f; 151 | 152 | #{ 153 | union: #[ 154 | #{ 155 | parameters: #{ 156 | x: string, 157 | y: #{ 158 | type: boolean, 159 | optional: true 160 | } 161 | }, 162 | return: number 163 | }, 164 | #{ 165 | parameters: #{ 166 | x: number 167 | }, 168 | return: string 169 | } 170 | ] 171 | } 172 | ``` 173 | 174 | Note, I don't like this setup using a tuple. I would much rather use a set if they were added as the order of signatures doesn't matter. 175 | 176 | #### Generic function 177 | 178 | Tentatively all generic parameters are included in the parameters. 179 | 180 | ```js 181 | function complex, V>( 182 | x: U, 183 | y: (t: T) => V, 184 | z: Map 185 | ): U { ... } 186 | 187 | #{ 188 | parameters: #{ 189 | T: #{ 190 | type: type, 191 | constraint: number 192 | }, 193 | U: #{ 194 | type: type, 195 | constraint: #{ 196 | type: Array, 197 | parameters: #[ 198 | #{ parameter: 'T' } 199 | ] 200 | } 201 | }, 202 | V: #{ 203 | type: type 204 | }, 205 | x: #{ 206 | type: #{ parameter: 'U' } 207 | }, 208 | y: #{ 209 | type: #{ 210 | parameters: #[ 211 | #{ 212 | name: 't', 213 | type: #{ parameter: 'T' } 214 | } 215 | ], 216 | return: #{ parameter: 'V' } 217 | } 218 | }, 219 | z: #{ 220 | type: #{ 221 | type: Map, 222 | parameters: #{ 223 | K: #{ parameter: 'V' }, 224 | V: #{ parameter: 'T' } 225 | } 226 | } 227 | } 228 | ], 229 | return: #{ parameter: 'U' } 230 | } 231 | ``` 232 | 233 | Is there any edge case where a parameter needs to be marked explicit/implicit? 234 | 235 | ### class type 236 | 237 | A class 238 | 239 | ```js 240 | const T = type interface { 241 | x: number; 242 | f: (value: number, ...foo: [].) => boolean; 243 | g: Generator<...>; 244 | }; 245 | 246 | #{ 247 | properties: #[ 248 | #{ 249 | name: 'x', 250 | type: number, 251 | public: true, 252 | private: false, 253 | static: false 254 | }, 255 | #{ 256 | name: 'f', 257 | parameters: #{ 258 | value: number, 259 | foo: #{ 260 | type: []., 261 | rest: true 262 | } 263 | ], 264 | public: true, 265 | private: false, 266 | static: false 267 | }, 268 | #{ 269 | name: 'g', 270 | type: Generator<...>, 271 | public: true, 272 | private: false, 273 | static: false 274 | } 275 | ] 276 | } 277 | ``` 278 | 279 | As mentioned in the spec, async types are just a Promise. 280 | TODO: Include example 281 | 282 | #### optional properties 283 | 284 | ```js 285 | #{ 286 | properties: #[ 287 | #{ 288 | name: 'x', 289 | type: #{ 290 | union: #[ 291 | number, 292 | undefined 293 | ] 294 | } 295 | } 296 | ] 297 | } 298 | ``` 299 | 300 | #### Generic class 301 | 302 | ```js 303 | class Pair { 304 | first: T; 305 | second: U; 306 | swap(): Pair { 307 | return new Pair(this.second, this.first); 308 | } 309 | } 310 | 311 | const T = type Pair; 312 | 313 | #{ 314 | parameters: #{ 315 | T: type, 316 | U: type 317 | ], 318 | properties: #[ 319 | #{ 320 | name: 'first', 321 | type: #{ parameter: 'T' } 322 | }, 323 | #{ 324 | name: 'second', 325 | type: #{ parameter: 'U' } 326 | }, 327 | #{ 328 | name: 'swap', 329 | type: #{ 330 | parameters: #{}, 331 | return: #{ 332 | type: Pair, 333 | parameters: #{ 334 | T: #{ parameter: 'U' } 335 | U: #{ parameter: 'T' } 336 | } 337 | } 338 | } 339 | } 340 | ] 341 | } 342 | ``` 343 | 344 | ### enum type 345 | 346 | ```js 347 | enum Count { Zero, One, Two } 348 | 349 | #{ 350 | values: #{ 351 | Zero: 0, 352 | One: 1, 353 | Two: 2 354 | } 355 | } 356 | ``` 357 | 358 | ```js 359 | enum Count: int32 { Zero, One, Two } 360 | 361 | #{ 362 | intersection: #[ 363 | int32, 364 | #{ 365 | values: #{ 366 | Zero: 0, 367 | One: 1, 368 | Two: 2 369 | } 370 | } 371 | ] 372 | } 373 | ``` 374 | 375 | ```js 376 | enum Count: float32 { Zero = 0, One = 100, Two = 200 } 377 | 378 | #{ 379 | intersection: #[ 380 | float32, 381 | #{ 382 | values: #{ 383 | Zero: 0, 384 | One: 100, 385 | Two: 200 386 | } 387 | } 388 | ] 389 | } 390 | ``` 391 | 392 | ```js 393 | enum Count: string { Zero = 'Zero', One = 'One', Two = 'Two', Three = 'Three' } 394 | 395 | #{ 396 | intersection: #[ 397 | string 398 | #{ 399 | values: #{ 400 | Zero: 'Zero', 401 | One: 'One', 402 | Two: 'Two', 403 | Three: 'Three' 404 | } 405 | } 406 | ] 407 | } 408 | ``` 409 | 410 | ```js 411 | enum Flags: uint32 { None = 0, Flag1 = 1, Flag2 = 2, Flag3 = 4 } 412 | 413 | #{ 414 | intersection: #[ 415 | uint32, 416 | #{ 417 | values: #{ 418 | None: 0, 419 | Flag1: 1, 420 | Flag2: 2, 421 | Flag3: 4 422 | } 423 | } 424 | ] 425 | } 426 | ``` 427 | 428 | ### Recursive types 429 | 430 | 431 | ### Record operators 432 | 433 | #### keyof 434 | 435 | returns a type with all the property keys 436 | 437 | ```js 438 | keyof T; 439 | 440 | #{ 441 | union: #[ 442 | 'a', 443 | 'b', 444 | 'f' 445 | ] 446 | } 447 | ``` 448 | 449 | Would need to make this iterable though ideally without just returning the tuple. 450 | 451 | #### Get property type by property name 452 | 453 | ```js 454 | TClass[propertyName] 455 | ``` 456 | 457 | #### Get parameter type by parameter name 458 | 459 | ```js 460 | TMethod[parameterName] 461 | ``` 462 | 463 | Note: This works for generic parameters also 464 | 465 | ### Notes 466 | 467 | I don't have a 'kind' applied to records. Should functions, classes, etc have a kind? Often their properties infer their kind. Is this sufficient? 468 | 469 | Operators to check extends true or false between two type records? 470 | 471 | If you add a new overload to a function dynamically, then previous type records would no longer match the new one. In practice what problems would this cause? 472 | 473 | ## Other Notes 474 | 475 | ### Getting parameter order or other metadata about functions? 476 | 477 | I'm thinking that there would be a ```type.info``` operator that returns a more verbose reflection of the current type definition. This would include all the overloads including their default values or references to their initializers. This would not be a record. It could also include serialization information. 478 | -------------------------------------------------------------------------------- /decorators.md: -------------------------------------------------------------------------------- 1 | # Decorators 2 | 3 | Very WIP: Feel free to open issues with fixes and requirements for sections. 4 | 5 | ## Introduction 6 | 7 | https://github.com/sirisian/ecmascript-types/issues/59 8 | https://github.com/sirisian/ecmascript-types/issues/65 9 | 10 | Types simplify how decorators are defined. By utilizing function overloading they get rid of the requirement to return a ```(value, context)``` function for decorators that take arguments. This means that independent of arguments a decorator looks the same. Modifying a decorator to take argument or take none just requires changing the parameters to the decorator. Consider these decorators that are all distinct: 11 | 12 | ```js 13 | function f(context) { 14 | // No parameters 15 | } 16 | function f(x:uint32, context) { 17 | // x is 0 18 | } 19 | function f(x:string, context) { 20 | // x is 'a' 21 | } 22 | 23 | class A { 24 | @f 25 | @f(0) 26 | @f('a') 27 | a:uint32; 28 | } 29 | ``` 30 | 31 | In the above the ```context``` is type ```any```. In actual usage one would use one of these types: 32 | 33 | ``` 34 | ClassDecorator 35 | ClassFieldDecorator 36 | ClassGetterDecorator 37 | ClassSetterDecorator 38 | ClassMethodDecorator 39 | ClassMethodParameterDecorator 40 | ClassOperatorDecorator 41 | // ClassOperatorParameterDecorator ? 42 | FunctionDecorator 43 | FunctionParameterDecorator 44 | ParameterDecorator 45 | LetDecorator 46 | ConstDecorator 47 | ObjectDecorator 48 | ObjectFieldDecorator 49 | ObjectGetterDecorator 50 | ObjectSetterDecorator 51 | ObjectMethodDecorator 52 | ObjectMethodParameterDecorator 53 | BlockDecorator 54 | InitializerDecorator 55 | ReturnDecorator 56 | EnumDecorator 57 | EnumEnumeratorDecorator 58 | TupleDecorator 59 | RecordDecorator 60 | ``` 61 | 62 | Decorators can be specialized for different types by specifying the generic parameters. They can also be specialized for different targets. 63 | 64 | ```js 65 | function f(context: ClassFieldDecorator) { 66 | console.log('decorator on uint32'); 67 | } 68 | function f(context: ClassFieldDecorator) { 69 | console.log('decorator on another type, extends A'); 70 | } 71 | function f(context: ClassFieldDecorator) { 72 | console.log('decorator on another type'); 73 | } 74 | 75 | class A { 76 | @f // decorator on uint32 77 | a:uint32 78 | 79 | @f // decorator on another type, extends A 80 | b:string 81 | } 82 | 83 | class B { 84 | @f // decorator on another type 85 | a:string 86 | } 87 | ``` 88 | 89 | Note that because rest parameters are allowed to be duplicated and placed anywhere this means it's legal to write: 90 | 91 | ```js 92 | function f(...x, context) { 93 | // [], [0, 1, 2], ['a', 'b', 'c'] 94 | } 95 | 96 | class A { 97 | @f 98 | a:bool 99 | 100 | @f(0, 1, 2) 101 | b:uint32 102 | 103 | @f('a', 'b', 'c') 104 | c:string 105 | } 106 | ``` 107 | 108 | An example featuring all of them: 109 | ```js 110 | function f(context: any) { 111 | } 112 | 113 | @f // ClassDecorator 114 | class A { 115 | @f // ClassFieldDecorator 116 | a:uint32 @f = 5 // InitializerDecorator 117 | @f 118 | #b:uint32 @f = 5 119 | 120 | @f // ClassGetterDecorator 121 | get c():@f uint32 { // ReturnDecorator 122 | } 123 | 124 | @f // ClassSetterDecorator 125 | set c(@f value:uint32) { // ParameterDecorator 126 | } 127 | 128 | @f // ClassMethodDecorator 129 | d(@f a:uint32):@f uint32 { 130 | } 131 | 132 | @f // ClassOperatorDecorator 133 | operator+(@f rhs):@f uint32 { 134 | } 135 | } 136 | 137 | @f // FunctionDecorator 138 | function g() {} 139 | 140 | @f 141 | let @f a @f = 5; // LetDecorator, InitializerDecorator 142 | 143 | const @f b @f = @f { // ConstDecorator, InitializerDecorator, BlockDecorator 144 | @f // ObjectFieldDecorator 145 | a: 1 146 | @f // ObjectMethodDecorator 147 | b: () => number; 148 | }; 149 | 150 | @f // EnumDecorator 151 | enum Count { @f Zero, One, Two }; // EnumEnumeratorDecorator 152 | 153 | const e = @f #[0]; // TupleDecorator 154 | 155 | const d = @f #{ a: 1 }; // RecordDecorator 156 | ``` 157 | 158 | ## Metadata 159 | 160 | Some contexts have metadata which is on the type essentially for classes. (This can be thought of as on the constructor). The same is true for enum types. For objects the metadata is on the instance. So each instance created has its own unique metadata that is not shared. 161 | 162 | ```js 163 | type Metadata = Record; 164 | ``` 165 | 166 | TODO: I want Metadata applied to types to be typed. Essentially I need a way for code to broadcast that they've registered a variable. IDEs would be able to see this. 167 | ```js 168 | @Metadata 169 | function f(context: ClassDecorator) { 170 | context.metadata.f = 'f'; 171 | } 172 | @f 173 | class A {} 174 | 175 | const metadata: Metadata = getMetadata(); 176 | metadata.f; 177 | ``` 178 | Obviously this would breakdown if a user dynamically generated a class. 179 | 180 | This would need to also work on properties. 181 | 182 | ```js 183 | @Metadata 184 | function f(context: ClassFieldDecorator) { 185 | context.metadata.f = 'f'; 186 | } 187 | class A { 188 | @f 189 | a: uint8; 190 | } 191 | 192 | const metadata: Metadata = getMetadata(); 193 | metadata.f; 194 | ``` 195 | 196 | The big picture of having these be typed is they could be treated as constant immutable records. If they were truly constant the values could be used with generics. An engine would also be free to optimize using the data. 197 | 198 | This might be possible independently if the programmer is diligent about utilizing records and tuples to store things. In common serialization scenarios the tuples would be iterated over and a very ambitious engine could customize the code path expanding the loop and inlining everything. 199 | 200 | ## Decorator Contexts 201 | 202 | Overloading parameter types and contexts allow defining specialized decorators for every situation. 203 | 204 | ### ClassDecorator 205 | ```js 206 | interface ClassDecorator): any }> { 207 | name: string | undefined; 208 | type: Function; 209 | metadata: Metadata; 210 | addInitializer(initializer: () => void): void; 211 | } 212 | ``` 213 | 214 | ### ClassFieldDecorator 215 | ```js 216 | interface ClassFieldDecorator { 217 | type: T; 218 | classType: TClass; 219 | classContext: ClassDecorator; 220 | name: string | symbol; 221 | static: boolean; 222 | private: boolean; 223 | metadata: Metadata; 224 | initial: T | undefined; // If a constant exists, then it'll be stored here 225 | addInitializer(initializer: () => void): void; 226 | } 227 | ``` 228 | 229 | ```js 230 | const metadataKey = Symbol('log'); 231 | function logField({ target, name, type, static, private, metadata }: ClassFieldDecorator) { 232 | console.log('name:', name); 233 | console.log('class:', targetContext.name); 234 | console.log('type:', type); 235 | console.log('static:', static); 236 | console.log('private:', private); 237 | // Similar TypeScript syntax: Reflect.defineMetadata(metadataKey, name, target.constructor, name); 238 | metadata[metadataKey] = name; 239 | } 240 | ``` 241 | 242 | ### ClassGetterDecorator 243 | ```js 244 | interface ClassGetterDecorator { 245 | type: () => T; 246 | classType: TClass; 247 | classContext: ClassDecorator; 248 | name: string | symbol; 249 | metadata: Metadata; 250 | addInitializer(initializer: () => void): void; 251 | } 252 | ``` 253 | 254 | ### ClassSetterDecorator 255 | ```js 256 | interface ClassSetterDecorator { 257 | type: (value: T) => void; 258 | classType: TClass; 259 | classContext: ClassDecorator; 260 | name: string | symbol; 261 | metadata: Metadata; 262 | addInitializer(initializer: () => void): void; 263 | } 264 | ``` 265 | 266 | ### ClassMethodDecorator 267 | ```js 268 | interface ClassMethodDecorator) => any, TClass> { 269 | type: T; 270 | classType: TClass; 271 | classContext: ClassDecorator; 272 | name: string | symbol; 273 | metadata: Metadata; 274 | } 275 | ``` 276 | 277 | ### ClassMethodParameterDecorator 278 | ```js 279 | interface ParameterDecorator { 280 | type: T; 281 | methodType: TMethod; 282 | methodContext: ClassMethodDecorator 283 | key: string | symbol; 284 | index: number; 285 | } 286 | ``` 287 | 288 | ### ClassOperatorDecorator 289 | ```js 290 | enum Operator: Symbol { 291 | AdditionAssignment 292 | SubtractionAssignment 293 | MultiplicationAssignment 294 | DivisionAssignment 295 | RemainderAssignment 296 | ExponentiationAssignment 297 | LeftShiftAssignment 298 | RightShiftAssignment 299 | UnsignedRightShiftAssignment 300 | BitwiseANDAssignment 301 | BitwiseXORAssignment 302 | BitwiseORAssignment 303 | Addition 304 | Subtraction 305 | Multiplication 306 | Division 307 | Remainder 308 | Exponentiation 309 | LeftShift 310 | RightShift 311 | UnsignedRightShift 312 | BitwiseAND 313 | BitwiseOR 314 | BitwiseXOR 315 | BitwiseNOT 316 | Equal 317 | NotEqual 318 | LessThan 319 | LessThanOrEqual 320 | GreaterThan 321 | GreaterThanOrEqual 322 | LogicalAND 323 | LogicalOR 324 | LogicalNOT 325 | Increment 326 | Decrement 327 | UnaryNegation 328 | UnaryPlus 329 | }; 330 | 331 | interface ClassOperatorDecorator { 332 | operator: Operator; 333 | type: (...args:[].) => any; 334 | metadata: Metadata; 335 | } 336 | ``` 337 | 338 | ```ClassOperatorParameterDecorator``` ? 339 | 340 | ### FunctionDecorator 341 | ```js 342 | interface FunctionDecorator) => any> { 343 | type: T; 344 | name: string | symbol | undefined; 345 | metadata: Metadata; 346 | } 347 | ``` 348 | 349 | ### FunctionParameterDecorator 350 | ```js 351 | interface ParameterDecorator { 352 | type: T; 353 | methodType: TMethod; 354 | methodContext: FunctionDecorator; 355 | key: string | symbol; 356 | index: number; 357 | metadata: Metadata; 358 | } 359 | ``` 360 | 361 | ### LetDecorator 362 | ```js 363 | interface LetDecorator { 364 | type: T; 365 | name: string; 366 | } 367 | ``` 368 | 369 | ### ConstDecorator 370 | ```js 371 | interface ConstDecorator { 372 | type: T; 373 | name: string; 374 | } 375 | ``` 376 | 377 | ### ObjectDecorator 378 | ```js 379 | interface ObjectDecorator { 380 | type: T; 381 | metadata: Metadata; // on the instance 382 | } 383 | ``` 384 | 385 | ```js 386 | function f(context: ObjectDecorator) { 387 | // ??? 388 | } 389 | 390 | const a = @f { 391 | (b: number): 10 392 | }; 393 | ``` 394 | 395 | ### ObjectFieldDecorator 396 | ```js 397 | interface ObjectFieldDecorator { 398 | type: T; 399 | objectType: TObject; 400 | objectContext: ObjectDecorator; 401 | key: string | symbol; 402 | metadata: Metadata; // on the instance 403 | } 404 | ``` 405 | 406 | ```js 407 | function f(context: ObjectFieldDecorator) { 408 | // ??? 409 | } 410 | 411 | const a = { 412 | @f 413 | (b: number): 10 414 | }; 415 | ``` 416 | 417 | ### ObjectGetterDecorator 418 | ```js 419 | interface ObjectGetterDecorator { 420 | type: () => T; 421 | objectType: TObject; 422 | objectContext: ObjectDecorator; 423 | name: string | symbol; 424 | metadata: Metadata; 425 | addInitializer(initializer: () => void): void; 426 | } 427 | ``` 428 | 429 | ### ObjectSetterDecorator 430 | ```js 431 | interface ObjectSetterDecorator { 432 | type: (value: T) => void; 433 | objectType: TObject; 434 | objectContext: ObjectDecorator; 435 | name: string | symbol; 436 | metadata: Metadata; 437 | addInitializer(initializer: () => void): void; 438 | } 439 | ``` 440 | 441 | ### ObjectMethodDecorator 442 | ```js 443 | interface ObjectMethodDecorator) => any, TObject> { 444 | type: T; 445 | objectType: TObject; 446 | objectContext: ObjectDecorator; 447 | key: string | symbol; 448 | metadata: Metadata; // on the instance 449 | } 450 | ``` 451 | 452 | ### ObjectMethodParameterDecorator 453 | ```js 454 | interface ParameterDecorator { 455 | type: T; 456 | objectType: TMethod; 457 | objectContext: ObjectMethodDecorator 458 | key: string | symbol; 459 | index: number; 460 | } 461 | ``` 462 | 463 | ### BlockDecorator 464 | ```js 465 | interface BlockDecorator { 466 | label?: string; // ??? 467 | insertBefore // ??? 468 | insertAfter // ??? 469 | wrap // ??? 470 | } 471 | ``` 472 | 473 | ```js 474 | function f(context: BlockDecorator) { 475 | 476 | } 477 | 478 | @f 479 | foo: // Should I be able to access labels in context? 480 | { 481 | const a = 10; 482 | } 483 | 484 | if (true) @f { 485 | 486 | } else @f { 487 | 488 | } 489 | 490 | while (true) @f { 491 | 492 | } 493 | 494 | do @f { 495 | 496 | } while(true); 497 | 498 | loop: 499 | for (let i = 0; i < 10; ++i) @f { 500 | 501 | } 502 | ``` 503 | 504 | Could include in the context all sorts of information like the scope information for current variables declarations/references. 505 | 506 | Loop blocks could also include their own context like the kind of loop, the initialization, condition, and increment expressions? 507 | 508 | ### InitializerDecorator 509 | ```js 510 | interface InitializerDecorator { 511 | target: Object; 512 | type: () => void; // Function that initializes something 513 | } 514 | ``` 515 | 516 | ### ReturnDecorator 517 | ```js 518 | interface ReturnDecorator { 519 | target: Object; 520 | propertyKey: string | symbol; 521 | type: T; 522 | } 523 | ``` 524 | 525 | ### EnumDecorator 526 | ```js 527 | interface EnumDecorator { 528 | type: T; 529 | metadata: Metadata; 530 | } 531 | ``` 532 | 533 | ### EnumEnumeratorDecorator 534 | ```js 535 | interface EnumEnumeratorDecorator { 536 | type: T; 537 | enumType: TEnum; 538 | enumContext: EnumDecorator; 539 | name: string; 540 | index: number; 541 | metadata: Metadata; 542 | } 543 | ``` 544 | 545 | ```js 546 | function label({ enumType, type, name, index }: EnumEnumeratorDecorator) { 547 | // Should I have the ability to reflect on T? reflectMetadata 548 | } 549 | 550 | enum Count { 551 | @label('Setting 1') 552 | Setting1, 553 | @label('Setting 2') 554 | Setting2 555 | }; 556 | ``` 557 | 558 | ```js 559 | enum Count: string { 560 | @label('None') 561 | Zero = (index, name) => name.toLowerCase(), 562 | @label('1 item') 563 | One, 564 | @label('2 items') 565 | Two 566 | } 567 | ``` 568 | 569 | ### TupleDecorator 570 | ```js 571 | interface TupleDecorator { 572 | target: Object; 573 | type: T; 574 | } 575 | ``` 576 | 577 | ### RecordDecorator 578 | ```js 579 | interface RecordDecorator { 580 | target: Object; 581 | type: Record; 582 | } 583 | ``` 584 | 585 | ## Examples 586 | 587 | WIP, this should be an exhaustive list of common decorator uses including metadata uses. 588 | 589 | ### Validation 590 | 591 | An existing TypeScript library for reference: https://github.com/typestack/class-validator 592 | 593 | A naive example below that assumes we only want to run a validation for the whole object. 594 | 595 | ```js 596 | const validatorsSymbol = Symbol('validators'); 597 | 598 | interface NameAndValidator { 599 | name: keyof T, 600 | validator: (value: T) => boolean 601 | }; 602 | 603 | function addValidators(({ name, classContext: { metadata } }: ClassFieldDecorator), validator: (value: T) => boolean) { 604 | (metadata[validatorsSymbol] ??= [].>).push({ name, validator }); 605 | } 606 | 607 | function Length(min: uint32, max: uint32, context: ClassFieldDecorator) { // Can only be placed on string 608 | addValidators(context, (value: string) => value.length is >= min and <= max); 609 | } 610 | function Includes(searchString: string, context: ClassFieldDecorator) { 611 | addValidators(context, (value: string) => value.includes(searchString) }); 612 | } 613 | function Min(min: T, context: ClassFieldDecorator) { 614 | addValidators(context, (value: T) => value >= min }); 615 | } 616 | function Max(max: T, context: ClassFieldDecorator) { 617 | addValidators(context, (value: T) => value <= max }); 618 | } 619 | function IsEmail(context: ClassFieldDecorator) { 620 | addValidators(context, (value: string) => value.includes('@') }); // :) 621 | } 622 | // ... 623 | 624 | function validate(o: T): boolean { 625 | return (Reflect.getMetadata(validatorsSymbol) as NameAndValidator[]).every(({ name, validator }) => validator(o[name])); 626 | } 627 | 628 | class Post { 629 | @Length(10, 20) 630 | title: string; 631 | 632 | @Contains('hello') 633 | text: string; 634 | 635 | //@IsInt() // Unnecessary 636 | @Min(0) 637 | @Max(10) 638 | rating: int32; 639 | 640 | @IsEmail() 641 | email: string; 642 | 643 | @IsFQDN() 644 | site: string; 645 | 646 | @IsZonedDateTime() 647 | createDate: Temporal.ZonedDateTime; 648 | } 649 | 650 | const post = new Post(); 651 | post.title = 'My post with a title...'; 652 | // ... 653 | validate(post); // true or false 654 | ``` 655 | 656 | ### Serialization 657 | 658 | const binary = Symbol('binary'); 659 | 660 | ```js 661 | function data(context: ClassFieldDecorator) { 662 | context.metadata[binary] = (packet, value) => packet.write(value); 663 | } 664 | 665 | function data(context: ClassFieldDecorator, any>) { 666 | context.metadata[binary] = (packet, value) => packet.write>(value); 667 | } 668 | function data(context: ClassFieldDecorator) { 669 | context.metadata[binary] = (packet, value: string) => packet.write(value); 670 | } 671 | 672 | @data 673 | class Room { 674 | id: uint64; 675 | @data(255) 676 | name: string; 677 | @data // Implicit generic uint 678 | test: uint<7>; 679 | @data 680 | currentlyBooked: boolean; 681 | } 682 | 683 | @data 684 | class Building { 685 | rooms: [].; 686 | } 687 | 688 | function binarySerialize(buffer: Packet, item: T) { 689 | const fields = Reflect.context(tag: string, { addInitializer }: ClassDecorator) { 700 | addInitializer(() => customElements.define(tag, T)); 701 | } 702 | 703 | @register('ui-tree') 704 | export class UITree extends HTMLElement { 705 | constructor() { 706 | super(); 707 | } 708 | } 709 | ``` 710 | 711 | ### Dependency injection 712 | 713 | ### Declarative routing 714 | 715 | ```js 716 | class Room { 717 | id: uint64; 718 | @validate(/[A-Za-z ]{1, 80}/) 719 | name: string; 720 | } 721 | 722 | typed RoomWithoutId = Omit; 723 | 724 | const rooms: []. = [ 725 | { id: 0, name: 'a' } 726 | ]; 727 | 728 | @route('rooms') 729 | class Rooms { 730 | @get('') 731 | getRooms(): []. { 732 | return rooms; 733 | } 734 | 735 | @get(':id') 736 | getRoomById(id: uint64): Room { 737 | const room = rooms.find(room => room.id === id); 738 | if (room === undefined) { 739 | throw new Error('id does not exist'); 740 | } 741 | return room; 742 | } 743 | 744 | @post('') 745 | addRoom({ name }: { name: string }): Room { 746 | if (rooms.find(room => room.name.toLowerCase() === name.toLowerCase())) { 747 | throw new Error('room name already exists'); 748 | } 749 | rooms.push({ name }); 750 | } 751 | 752 | @post(':id') 753 | editRoom(id: uint64, { name }: Room | RoomWithoutId): Room { 754 | const room = rooms.find(room => room.id === id); 755 | if (room === undefined) { 756 | throw new Error('id does not exist'); 757 | } 758 | room.name = name; 759 | } 760 | } 761 | ``` 762 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECMAScript Proposal: Optional Static Typing 2 | 3 | Current status of this proposal is -1. It's in a theoretical state at the moment to better understand how types could function in Javascript and the long-term future benefits or complications they could cause to future proposals. 4 | 5 | ## Rationale 6 | 7 | With TypedArrays and classes finalized, ECMAScript is in a good place to finally discuss types again. The demand for types as a different approach to code has been so strong in the past few years that separate languages have been created to deal with the perceived shortcomings. Types won't be an easy discussion, nor an easy addition, since they touch a large amount of the language; however, they are something that needs rigorous discussion. 8 | 9 | The types described below bring ECMAScript in line or surpasses the type systems in most languages. For developers it cleans up a lot of the syntax, as described later, for TypedArrays, SIMD, and working with number types (floats vs signed and unsigned integers). It also allows for new language features like function overloading and a clean syntax for operator overloading. For implementors, added types offer a way to better optimize the JIT when specific types are used. For languages built on top of Javascript this allows more explicit type usage and closer matching to hardware. 10 | 11 | The explicit goal of this proposal is to not just to give developers static type checking. It's to offer information to engines to use native types and optimize callstacks and memory usage. Ideally engines could inline and optimize code paths that are fully typed offering closer to native performance. 12 | 13 | ### Native/Runtime Typing vs Type Annotations aka Types as Comments 14 | 15 | This proposal covers a native/runtime type system and associated language features. That is the types introduced are able to be used by the engine to implement new features and optimize code. Errors related to passing the wrong types throws ```TypeError``` exceptions meaning the types are validated at runtime. 16 | 17 | A type annotation or types as comments proposal treats type syntax as comments with no impact on the behavior of the code. It's primarily used with bundlers and IDEs to run checks during development. See the Type Annotations proposal for more details. 18 | 19 | ### Types Proposed 20 | - [x] In Proposal Specification 21 | 22 | Since it would be potentially years before this would be implemented this proposal includes a new keyword ```enum``` for enumerated types and the following types: 23 | 24 | ```number``` 25 | ```boolean``` 26 | ```string``` 27 | ```object``` 28 | ```symbol``` 29 | ```int.``` 30 |
31 | Expand for all the int shorthands. 32 | 33 | ```js 34 | type int8 = int.<8>; 35 | type int16 = int.<16>; 36 | type int32 = int.<32>; 37 | type int64 = int.<64>; 38 | ``` 39 |
40 | 41 | ```uint.``` 42 |
43 | Expand for all the uint shorthands. 44 | 45 | ```js 46 | type uint8 = uint.<8>; 47 | type uint16 = uint.<16>; 48 | type uint32 = uint.<32>; 49 | type uint64 = uint.<64>; 50 | ``` 51 |
52 | 53 | ```bigint``` 54 | ```float16```, ```float32```, ```float64```, ```float80```, ```float128``` 55 | ```decimal32```, ```decimal64```, ```decimal128``` 56 | ```vector.``` 57 | 58 |
59 | Expand for all the SIMD shorthands. 60 | 61 | ```js 62 | type boolean1 = uint.<1>; 63 | type boolean8 = vector.; 64 | type boolean16 = vector.; 65 | type boolean32 = vector.; 66 | type boolean64 = vector.; 67 | 68 | type boolean8x16 = vector.; 69 | type boolean16x8 = vector.; 70 | type boolean32x4 = vector.; 71 | type boolean64x2 = vector.; 72 | type boolean8x32 = vector.; 73 | type boolean16x16 = vector.; 74 | type boolean32x8 = vector.; 75 | type boolean64x4 = vector.; 76 | type int8x16 = vector.; 77 | type int16x8 = vector.; 78 | type int32x4 = vector.; 79 | type int64x2 = vector.; 80 | type int8x32 = vector.; 81 | type int16x16 = vector.; 82 | type int32x8 = vector.; 83 | type int64x4 = vector.; 84 | type uint8x16 = vector.; 85 | type uint16x8 = vector.; 86 | type uint32x4 = vector.; 87 | type uint64x2 = vector.; 88 | type uint8x32 = vector.; 89 | type uint16x16 = vector.; 90 | type uint32x8 = vector.; 91 | type uint64x4 = vector.; 92 | type float32x4 = vector.; 93 | type float64x2 = vector.; 94 | type float32x8 = vector.; 95 | type float64x4 = vector.; 96 | ``` 97 |
98 | 99 | ```rational``` 100 | ```complex``` 101 | ```any``` 102 | 103 | These types once imported behave like a ```const``` declaration and cannot be reassigned. 104 | 105 | ### Variable Declaration With Type 106 | - [x] In Proposal Specification 107 | - [x] Proposal Specification Grammar 108 | - [ ] Proposal Specification Algorithms 109 | 110 | This syntax is taken from ActionScript and other proposals over the years. It's subjectively concise, readable, and consistent throughout the proposal. 111 | 112 | ```js 113 | var a: Type = value; 114 | let b: Type = value; 115 | const c: Type = value; 116 | ``` 117 | 118 | ### typeof Operator 119 | - [ ] In Proposal Specification 120 | - [ ] Proposal Specification Grammar 121 | - [ ] Proposal Specification Algorithms 122 | 123 | ```typeof```'s behavior is essentially unchanged. All numerical types return ```"number"```. SIMD, rational, and complex types return ```"object"```. 124 | 125 | ```js 126 | let a: uint8 = 0; // typeof a == "number" 127 | let b: uint8|null = 0; // typeof b == "number" 128 | let c: []. = []; // typeof c == "object" 129 | let d: (uint8) => uint8 = x => x * x; // typeof d == "function" 130 | ``` 131 | 132 | TODO: Should there be a way to get the specific type? See https://github.com/sirisian/ecmascript-types/issues/60 133 | 134 | ### instanceof Operator 135 | - [ ] In Proposal Specification 136 | - [ ] Proposal Specification Grammar 137 | - [ ] Proposal Specification Algorithms 138 | 139 | THIS SECTION IS A WIP 140 | 141 | ```js 142 | if (a instanceof uint8) {} 143 | ``` 144 | 145 | Also this would be nice for function signatures. 146 | 147 | ```js 148 | if (a instanceof (uint8) => uint8) {} 149 | ``` 150 | 151 | That would imply ```Object.getPrototypeOf(a) === ((uint8):uint8).prototype```. 152 | 153 | I'm not well versed on if this makes sense though, but it would be like each typed function has a prototype defined by the signature. 154 | 155 | ### Union and Nullable Types 156 | - [ ] In Proposal Specification 157 | - [ ] Proposal Specification Grammar 158 | - [ ] Proposal Specification Algorithms 159 | 160 | All types except ```any``` are non-nullable. The syntax below creates a nullable ```uint8``` typed variable: 161 | ```js 162 | let a: uint8 | null = null; 163 | ``` 164 | 165 | A union type can be defined like: 166 | ```js 167 | let a: uint8 | string = 'a'; 168 | ``` 169 | 170 | The ```|``` can placed at the beginning when defining a union across multiple lines. 171 | ```js 172 | type a = 173 | | b 174 | | c; 175 | ``` 176 | 177 | ### Intersection types 178 | 179 | // TODO 180 | 181 | ### any Type 182 | - [ ] In Proposal Specification 183 | - [ ] Proposal Specification Algorithms 184 | 185 | Using ```any|null``` would result in a syntax error since ```any``` already includes nullable types. As would using ```[].``` since it already includes array types. Using just ```[]``` would be the type for arrays that can contain anything. For example: 186 | 187 | ```js 188 | let a:[]; 189 | ``` 190 | 191 | ### Variable-length Typed Arrays 192 | - [x] In Proposal Specification 193 | - [x] Proposal Specification Grammar 194 | - [ ] Proposal Specification Algorithms 195 | 196 | A generic syntax ```.``` is used to type array elements. 197 | 198 | ```js 199 | let a: [].; // [] 200 | a.push(0); // [0] 201 | let b: []. = [0, 1, 2, 3]; 202 | let c: []. | null; // null 203 | let d: []. = [0, null]; // Not sequential memory 204 | let e: [].|null; // null // Not sequential memory 205 | ``` 206 | 207 | The index operator doesn't perform casting just to be clear so array objects even when typed still behave like objects. 208 | 209 | ```js 210 | let a: []. = [0, 1, 2, 3]; 211 | a['a'] = 0; 212 | 'a' in a; // true 213 | delete a['a']; 214 | ``` 215 | 216 | ### Fixed-length Typed Arrays 217 | - [x] In Proposal Specification 218 | - [x] Proposal Specification Grammar 219 | - [ ] Proposal Specification Algorithms 220 | 221 | ```js 222 | let a: [4].; // [0, 0, 0, 0] 223 | // a.push(0); TypeError: a is fixed-length 224 | // a.pop(); TypeError: a is fixed-length 225 | a[0] = 1; // valid 226 | // a[a.length] = 2; Out of range 227 | let b: [4]. = [0, 1, 2, 3]; 228 | let c: [4]. | null; // null 229 | ``` 230 | 231 | Typed arrays would be zero-ed at creation. That is the allocated memory would be set to all zeroes. 232 | 233 | Also all fixed-length typed arrays use a SharedArrayBuffer by default. 234 | 235 | ### Mixing Variable-length and Fixed-length Arrays 236 | 237 | ```js 238 | function f(c:boolean):[]. { // default case, return a resizable array 239 | let a: [4]. = [0, 1, 2, 3]; 240 | let b: [6]. = [0, 1, 2, 3, 4, 5]; 241 | return c ? a : b; 242 | } 243 | 244 | function f(c:boolean):[6]. { // Resizes a if c is true 245 | let a: [4]. = [0, 1, 2, 3]; 246 | let b: [6]. = [0, 1, 2, 3, 4, 5]; 247 | return c ? a : b; 248 | } 249 | ``` 250 | 251 | ### Any Typed Array 252 | - [x] In Proposal Specification 253 | - [x] Proposal Specification Grammar 254 | 255 | ```js 256 | let a: []; // Using []. is a syntax error as explained before 257 | let b: [] | null; // null 258 | ``` 259 | 260 | Deleting a typed array element results in a type error: 261 | 262 | ```js 263 | const a: []. = [0, 1, 2, 3]; 264 | // delete a[0]; TypeError: a is fixed-length 265 | ``` 266 | 267 | ### Array length Type And Operations 268 | - [x] In Proposal Specification 269 | - [x] Proposal Specification Grammar 270 | - [ ] Proposal Specification Algorithms 271 | 272 | Valid types for defining the length of an array are ```int8```, ```int16```, ```int32```, ```int64```, ```uint8```, ```uint16```, ```uint32```, and ```uint64```. 273 | 274 | ```js 275 | []. 276 | ``` 277 | 278 | Syntax uses the second parameter for the generic: 279 | 280 | ```js 281 | let a: []. = [0, 1, 2, 3, 4]; 282 | let b = a.length; // length is type int8 283 | ``` 284 | 285 | ```js 286 | let a: [5]. = [0, 1, 2, 3, 4]; 287 | let b = a.length; // length is type uint64 with value 5 288 | ``` 289 | 290 | ```js 291 | let n = 5; 292 | let a: [n]. = [0, 1, 2, 3, 4]; 293 | let b = a.length; // length is type uint64 with value 5 294 | ``` 295 | 296 | Setting the ```length``` reallocates the array truncating when applicable. 297 | 298 | ```js 299 | let a: []. = [0, 1, 2, 3, 4]; 300 | a.length = 4; // [0, 1, 2, 4] 301 | a.length = 6; // [0, 1, 2, 4, 0, 0] 302 | ``` 303 | 304 | ```js 305 | let a:[5]. = [0, 1, 2, 3, 4]; 306 | // a.length = 4; TypeError: a is fixed-length 307 | ``` 308 | 309 | ### Array Views 310 | - [x] [Proposal Specification Grammar](https://sirisian.github.io/ecmascript-types/#prod-ArrayView) 311 | - [ ] Proposal Specification Algorithms 312 | 313 | Like ```TypedArray``` views, this array syntax allows any array, even arrays of typed objects to be viewed as different objects. 314 | 315 | ```js 316 | let view = [].(buffer [, byteOffset [, byteElementLength]]); 317 | ``` 318 | 319 | ```js 320 | let a: []. = [1]; 321 | let b = [].(a, 0, 8); 322 | ``` 323 | 324 | By default ```byteElementLength``` is the size of the array's type. So ```[].(...)``` would be 4 bytes. The ```byteElementLength``` can be less than or greater than the actual size of the type. For example (refer to the Class section): 325 | 326 | ```js 327 | class A { 328 | a:uint8; 329 | b:uint16; 330 | constructor(value) { 331 | this.b = value; 332 | } 333 | } 334 | const a:[].
= [0, 1, 2]; 335 | const b = [].(a, 1, 3); // Offset of 1 byte into the array and 3 byte length per element 336 | b[2]; // 2 337 | ``` 338 | 339 | ### Multidimensional and Jagged Array Support Via User-defined Index Operators 340 | - [ ] In Proposal Specification 341 | - [ ] Proposal Specification Grammar 342 | - [ ] Proposal Specification Algorithms 343 | 344 | Rather than defining index functions for various multidimensional and jagged array implementations the user is given the ability to define their own. More than one can be defined as long as they have unique signatures. 345 | 346 | An example of a user-defined index to access a 16 element grid with ```(x, y)``` coordinates: 347 | 348 | ```js 349 | class GridArray extends [N]. { 350 | get operator[](x: uint32, y: uint32) { 351 | return ref this[y * 4 + x]; 352 | } 353 | } 354 | const grid = new GridArray<16>(); 355 | grid[2, 1] = 10; 356 | ``` 357 | 358 | ```js 359 | class GridArray extends [N]. { 360 | get operator[](i: uint32) { 361 | return ref this[i]; 362 | } 363 | get operator[](x: uint32, y: uint32) { 364 | return ref this[y * 4 + x]; 365 | } 366 | } 367 | const grid = new GridArray<16>(); 368 | grid[0] = 10; 369 | grid[2, 1] = 10; 370 | ``` 371 | 372 | For a variable-length array it works as expected: 373 | 374 | ```js 375 | class GridArray extends []. { 376 | get operator[](x: uint32, y: uint32, z: uint32) { 377 | return ref this[z * 4**2 + y * 4 + x]; 378 | } 379 | } 380 | const grid = new GridArray(); 381 | grid.push(...); 382 | grid[1, 2] = 10; 383 | ``` 384 | 385 | Views also work as expected allowing one to apply custom indexing to existing arrays: 386 | 387 | ```js 388 | const grid = new [100].(); 389 | const gridView = new GridArray(grid); 390 | ``` 391 | 392 | ### Implicit Casting 393 | - [ ] In Proposal Specification 394 | - [ ] Proposal Specification Algorithms 395 | 396 | The default numeric type Number would convert implicitly with precedence given to ```decimal128/64/32```, ```float128/80/64/32/16```, ```uint64/32/16/8```, ```int64/32/16/8```. (This is up for debate). Examples are shown later with class constructor overloading. 397 | 398 | ```js 399 | function f(a: float32) {} 400 | function f(a: uint32) {} 401 | f(1); // float32 called 402 | f(1 as uint32); // uint32 called 403 | ``` 404 | 405 | It's also possible to use operator overloading to define implicit casts. The following casts to a heterogeneous tuple: 406 | 407 | ```js 408 | class A { 409 | x: number; 410 | y: number; 411 | z: string; 412 | operator [number, number, string]() { 413 | return [this.x, this.y, this.z]; 414 | } 415 | } 416 | 417 | const a = new A(); 418 | const [x, y, z] = A; 419 | ``` 420 | 421 | ### Explicit Casting 422 | - [ ] In Proposal Specification 423 | - [ ] Proposal Specification Algorithms 424 | 425 | ```js 426 | let a := 65535 as uint8; // Cast taking the lowest 8 bits so the value 255, but note that a is still typed as any 427 | let b: uint8 = 65535; // Same as the above 428 | ``` 429 | 430 | Many truncation rules have intuitive rules going from larger bits to smaller bits or signed types to unsigned types. Type casts like decimal to float or float to decimal would need to be clear. 431 | 432 | ### Function signatures with constraints 433 | - [x] Proposal Specification Grammar 434 | - [ ] Proposal Specification Algorithms 435 | 436 | A typed function defaults to a return type of ```undefined```. In almost every case where ```undefined``` might be needed it's implicit and defining it is not allowed. 437 | ```js 438 | function f() {} // return type any 439 | // function f(a: int32) { return 10; } // TypeError: Function signature for F, undefined, does not match return type, number. 440 | function g(a: int32) {} // return type undefined 441 | // function g(a: int32):undefined {} // TypeError: Explicitly defining a return type of undefined is not allowed. 442 | ``` 443 | The only case where ```undefined``` is allowed is for functions that take no parameters where the return type signals it's a typed function. 444 | ```js 445 | function f(): undefined {} 446 | ``` 447 | 448 | An example of applying more parameter constraints: 449 | ```js 450 | function f(a: int32, b: string, c: []., callback: (boolean, string) => string = (b, s = 'none') => b ? s : ''): int32 {} 451 | ``` 452 | 453 | #### Optional Parameters 454 | 455 | While function overloading can be used to handle many cases of optional arguments it's possible to define one function that handles both: 456 | 457 | ```js 458 | function f(a: uint32, b?: uint32) {} 459 | f(1); 460 | f(1, 2); 461 | ``` 462 | 463 | ### Typed Arrow Functions 464 | - [x] Proposal Specification Grammar 465 | - [ ] Proposal Specification Algorithms 466 | 467 | ```js 468 | let a: (int32, string) => string; // hold a reference to a signature of this type 469 | let b: (); // undefined is the default return type for a signature without a return type 470 | let c = (s: string, x: int32) => s + x; // implicit return type of string 471 | let d = (x: uint8, y: uint8): uint16 => x + y; // explicit return type 472 | let e = x: uint8 => x + y; // single parameter 473 | ``` 474 | Like other types they can be made nullable. An example showing an extreme case where everything is made nullable: 475 | ```js 476 | let a: ((number | null) => number | null) | null = null; 477 | ``` 478 | This can be written also using the interfaces syntax, which is explained later: 479 | ```js 480 | let a: { (uint32 | null): uint32; } | null = null; 481 | ``` 482 | 483 | ### Integer Binary Shifts 484 | - [ ] Proposal Specification Algorithms 485 | 486 | ```js 487 | let a: int8 = -128; 488 | a >> 1; // -64, sign extension 489 | let b: uint8 = 128; 490 | b >> 1; // 64, no sign extension as would be expected with an unsigned type 491 | ``` 492 | 493 | ### Integer Division 494 | - [ ] Proposal Specification Algorithms 495 | 496 | ```js 497 | let a: int32 = 3; 498 | a /= 2; // 1 499 | ``` 500 | 501 | ### Type Propagation to Literals 502 | - [ ] In Proposal Specification 503 | - [ ] Proposal Specification Algorithms 504 | 505 | In ECMAScript currently the following values are equal: 506 | 507 | ```js 508 | let a = 2**53; 509 | a == a + 1; // true 510 | ``` 511 | The changes below expand the representable numbers by propagating type information when defined. 512 | 513 | Types propagate to the right hand side of any expression. 514 | 515 | ```js 516 | let a: uint64 = 2**53; 517 | a == a + 1; // false 518 | 519 | let b:uint64 = 9007199254740992 + 9007199254740993; // 18014398509481985 520 | ``` 521 | 522 | Types propagate to arguments as well. 523 | 524 | ```js 525 | function f(a: uint64) {} 526 | f(9007199254740992 + 9007199254740993); // 18014398509481985 527 | ``` 528 | 529 | Consider where the literals are not directly typed. In this case they are typed as Number as expected: 530 | 531 | ```js 532 | function f(a: uint64) {} 533 | const a = 9007199254740992 + 9007199254740993; 534 | f(a); // 18014398509481984 535 | ``` 536 | 537 | In typed code this behavior of propagating types to literals means that suffixes aren't required by programmers. 538 | 539 | This proposal introduces one breaking change related to the BigInt function. When passing an expression the signature uses ```bigint(n:bigint)```. 540 | 541 | ```js 542 | //BigInt(999999999999999999999999999999999999999999); // Current behavior is 1000000000000000044885712678075916785549312n 543 | BigInt(999999999999999999999999999999999999999999); // New Behavior: 999999999999999999999999999999999999999999n 544 | ``` 545 | Alternatively BigInt could remain as it is and ```bigint``` would have this behavior. The change is only made to avoid confusion. 546 | 547 | This behavior is especially useful when using the float and decimal types. 548 | 549 | ```js 550 | const a: decimal128 = 9.999999999999999999999999999999999; 551 | ``` 552 | 553 | ### Typed Array Propagation to Arrays 554 | 555 | Identically to how types propagate to literals they also propagate to arrays. For example, the array type is propagated to the right side: 556 | ```js 557 | const a:[]. = [999999999999999999999999999999999999999999]; 558 | ``` 559 | 560 | This can be used to construct instances using implicit casting: 561 | ```js 562 | class MyType { 563 | constructor(a: uint32) { 564 | } 565 | constructor(a: uint32, b: uint32) { 566 | } 567 | } 568 | let a:[]. = [1, 2, 3, 4, 5]; 569 | ``` 570 | 571 | Implicit array casting already exists for single variables as defined above. It's possible one might want to compactly create instances. The following new syntax allows this: 572 | 573 | ```js 574 | let a: []. = [(10, 20), (30, 40), 10]; 575 | ``` 576 | This would be equivalent to: 577 | ```js 578 | let a: []. = [new MyType(10, 20), new MyType(30, 40), 10]; 579 | ``` 580 | 581 | Due to the very specialized syntax it can't be introduced later. In ECMAScript the parentheses have defined meaning such that ```[(10, 20), 30]``` is ```[20, 30]``` when evaluated. This special syntax takes into account that an array is being created requiring more grammar rules to specialize this case. 582 | 583 | Initializer lists work well with SIMD to create compact arrays of vectors: 584 | 585 | ```js 586 | let a: []. = [ 587 | (1, 2, 3, 4), (1, 2, 3, 4), (1, 2, 3, 4), 588 | (1, 2, 3, 4), (1, 2, 3, 4), (1, 2, 3, 4), 589 | (1, 2, 3, 4), (1, 2, 3, 4), (1, 2, 3, 4) 590 | ]; 591 | ``` 592 | 593 | Since this works for any type the following works as well. The typed array is propagated to the argument. 594 | ```js 595 | function f(a: [].) { 596 | } 597 | f([(1, 2, 3, 4)]); 598 | ``` 599 | 600 | ### Destructuring Assignment Casting 601 | - [x] Proposal Specification Grammar 602 | - [ ] Proposal Specification Algorithms 603 | 604 | Array destructuring with default values: 605 | 606 | ```js 607 | [a: uint32 = 1, b: float32 = 2] = f(); 608 | ``` 609 | 610 | Object destructuring with default values: 611 | 612 | ```js 613 | { (a: uint8) = 1, (b: uint8) = 2 } = { a: 2 }; 614 | ``` 615 | 616 | Object destructuring with default value and new name: 617 | 618 | ```js 619 | let { (a: uint8): b = 1 } = { a: 2 }; // b is 2 620 | ``` 621 | 622 | Assigning to an already declared variable: 623 | 624 | ```js 625 | let b:uint8; 626 | ({ a: b = 1 } = { a: 2 }); // b is 2 627 | ``` 628 | 629 | Destructuring with functions: 630 | 631 | ```js 632 | (({ (a: uint8): b = 0, (b: uint8): a = 0}, [c: uint8]) => 633 | { 634 | // a = 2, b = 1, c = 0 635 | })({a: 1, b: 2}, [0]); 636 | ``` 637 | 638 | Nested/deep object destructuring: 639 | 640 | ```js 641 | const { a: { (a2: uint32): b, a3: [, c: uint8] } } = { a: { a2: 1, a3: [2, 3] } }; // b is 1, c is 3 642 | ``` 643 | 644 | Destructuring objects with arrays: 645 | 646 | ```js 647 | const { (a: [].) } = { a: [1, 2, 3] } }; // a is [1, 2, 3] with type []. 648 | ``` 649 | 650 | ### Array Rest Destructuring 651 | 652 | ```js 653 | let [a: uint8, ...[b: uint8]] = [1, 2]; 654 | b; // 2 655 | ``` 656 | 657 | A recursive spread version that is identical, but shown for example: 658 | 659 | ```js 660 | let [a: uint8, ...[...[b: uint8]]] = [1, 2]; 661 | b; // 2 662 | ``` 663 | 664 | Typing arrays: 665 | 666 | ```js 667 | let [a: uint8, ...b: uint8] = [1, 2]; 668 | b; // [2] 669 | ``` 670 | 671 | ### Object Rest Destructuring 672 | 673 | https://github.com/tc39/proposal-object-rest-spread 674 | 675 | ```js 676 | let { (x: uint8), ...(y:{ (a: uint8), (b: uint8) }) } = { x: 1, a: 2, b: 3 }; 677 | x; // 1 678 | y; // { a: 2, b: 3 } 679 | ``` 680 | 681 | Renaming: 682 | 683 | ```js 684 | let { (x: uint8): a, ...(b:{ (a: uint8): x, (b: uint8): y }) } = { x: 1, a: 2, b: 3 }; 685 | a; // 1 686 | b; // { x: 2, y: 3 } 687 | ``` 688 | 689 | ### Typed return values for destructuring 690 | - [x] Proposal Specification Grammar 691 | - [ ] Proposal Specification Algorithms 692 | 693 | Basic array destructuring: 694 | ```js 695 | function f(): [uint8, uint32] { 696 | return [1, 2]; 697 | } 698 | const [a, b] = f(); 699 | ``` 700 | 701 | Array defaults 702 | ```js 703 | function f(): [uint8, uint32 = 10] { 704 | return [1]; 705 | } 706 | const [a, b] = f(); // a is 1 and b is 10 707 | ``` 708 | 709 | Basic object destructuring: 710 | ```js 711 | function f(): { a: uint8; b: float32; } { 712 | return { a: 1, b: 2 }; 713 | } 714 | const { a, b } = f(); 715 | ``` 716 | 717 | Object defaults: 718 | ```js 719 | function f():{ a: uint8; b: float32 = 10; } { 720 | return { a: 1 }; 721 | } 722 | const { a, b } = f(); // { a: 1, b: 10 } 723 | ``` 724 | 725 | Overloaded example for the return type: 726 | ```js 727 | function f(): [int32] { 728 | return [1]; 729 | } 730 | function f(): [int32, int32] { 731 | return [2, 3]; 732 | } 733 | function f(): { a: uint8; b: float32; } { 734 | return { a: 1, b: 2 }; 735 | } 736 | const [a] = f(); // a is 1 737 | const [b, ...c] = f(); // b is 2 and c is [3] 738 | const { a: d, b: e } = f(); // d is 1 and e is 2 739 | ``` 740 | See the section on overloading return types for more information: https://github.com/sirisian/ecmascript-types#overloading-on-return-type 741 | 742 | Explicitly selecting an overload: 743 | ```js 744 | function f(): [int32] { 745 | return [1]; 746 | } 747 | function f(): [float32] { 748 | return [2.0]; 749 | } 750 | const [a: int32] = f(); 751 | const [a: float32] = f(); 752 | ``` 753 | 754 | TypeError example: 755 | 756 | ```js 757 | function f(): [int32, float32] { 758 | // return [1]; // TypeError, expected [int32, float32] 759 | } 760 | ``` 761 | 762 | ### Interfaces 763 | - [ ] Proposal Specification Grammar 764 | - [ ] Proposal Specification Algorithms 765 | 766 | Interfaces can be used to type objects, arrays, and functions. This allows users to remove redundant type information that is used in multiple places such as in destructuring calls. In addition, interfaces can be used to define contracts for classes and their required properties. 767 | 768 | #### Object Interfaces 769 | - [ ] Proposal Specification Grammar 770 | - [ ] Proposal Specification Algorithms 771 | 772 | ```js 773 | interface IExample { 774 | a: string; 775 | b: (uint32) => uint32; 776 | ?c: any; // Optional property. A default value can be assigned like: 777 | // c: any = []; 778 | } 779 | 780 | function f(): IExample { 781 | return { a: 'a', b: x => x }; 782 | } 783 | ``` 784 | 785 | Similar to other types an object interface can be made nullable and also made into an array with ```[]```. 786 | 787 | ```js 788 | function f(a: []. | null) { 789 | } 790 | ``` 791 | 792 | An object that implements an interface cannot be modified in a way that removes that implementation. 793 | 794 | ```js 795 | interface IExample { 796 | a: string; 797 | } 798 | function f(a: IExample) { 799 | // delete a.a; // TypeError: Property 'a' in interface IExample cannot be deleted 800 | } 801 | f({ a: 'a' }); 802 | ``` 803 | In this example the object argument is cast to an IExample since it matches the shape. 804 | 805 | A more complex example: 806 | 807 | ```js 808 | interface A { a: uint32; } 809 | interface B { a: string; } 810 | function f(a: A) {} 811 | function f(b: B) {} 812 | function g(a: A | B) { 813 | a.a = 10; // "10" because parameter 'a' implements B 814 | } 815 | g({ a: 'a' }); 816 | ``` 817 | 818 | #### Array Interfaces 819 | - [ ] Proposal Specification Grammar 820 | - [ ] Proposal Specification Algorithms 821 | 822 | ```js 823 | interface IExample [ 824 | string, 825 | uint32, 826 | ?string // Optional item. A default value can be assigned like: 827 | // ?string = 10 828 | ] 829 | ``` 830 | 831 | ```js 832 | function f(): IExample { 833 | return ['a', 1]; 834 | } 835 | ``` 836 | 837 | #### Function Interfaces 838 | - [ ] Proposal Specification Grammar 839 | - [ ] Proposal Specification Algorithms 840 | 841 | With function overloading an interface can place multiple function constraints. Unlike parameter lists in function declarations the type precedes the optional name. 842 | 843 | ```js 844 | interface IExample { 845 | (string, uint32); // undefined is the default return type 846 | (uint32); 847 | ?(string, string): string; // Optional overload. A default value can be assigned like: 848 | // (string, string): string = (x, y) => x + y; 849 | } 850 | ``` 851 | 852 | ```js 853 | function f(a:IExample) { 854 | a('a', 1); 855 | // a('a'); // TypeError: No matching signature for (string). 856 | } 857 | ``` 858 | 859 | Signature equality checks ignore renaming: 860 | 861 | ```js 862 | interface IExample { 863 | ({ (a: uint32) }): uint32 864 | } 865 | function f(a: IExample) { 866 | a({ a: 1 }); // 1 867 | } 868 | f(({(a:uint32):b}) => b); // This works since the signature check ignores any renaming 869 | ``` 870 | 871 | An example of taking a typed object: 872 | ```js 873 | interface IExample { 874 | ({ a: uint32; }): uint32; 875 | } 876 | function f(a:IExample) { 877 | a({ a: 1 }); // 1 878 | } 879 | f(a => a.a); 880 | ``` 881 | 882 | Argument names in function interfaces are optional. This to support named arguments. Note that if an interface is used then the name can be changed in the passed in function. For example: 883 | 884 | ```js 885 | interface IExample { 886 | (string = 5, uint32: named); 887 | } 888 | function f(a: IExample) { 889 | a(named: 10); // 10 890 | } 891 | f((a, b) => b); 892 | ``` 893 | 894 | The interface in this example defines the mapping for "named" to the second parameter. 895 | 896 | It might not be obvious at first glance, but there are two separate syntaxes for defining function type constraints. One without an interface, for single non-overloaded function signatures, and with interface, for either constraining the parameter names or to define overloaded function type constraints. 897 | 898 | ```js 899 | function (a: (uint32, uint32)) {} // Using non-overloaded function signature 900 | function (a: { (uint32, uint32); }) {} // Identical to the above using Interface syntax 901 | ``` 902 | Most of the time users will use the first syntax, but the latter can be used if a function is overloaded: 903 | ```js 904 | function (a: { (uint32); (string); }) { 905 | a(1); 906 | a('a'); 907 | } 908 | ``` 909 | 910 | #### Nested Interfaces 911 | 912 | ```js 913 | interface IA { 914 | a: uint32; 915 | } 916 | interface IB { 917 | (IA); 918 | } 919 | /* 920 | interface IB { 921 | ({ a: uint32; }); 922 | } 923 | */ 924 | ``` 925 | 926 | #### Extending Interfaces 927 | - [ ] Proposal Specification Grammar 928 | - [ ] Proposal Specification Algorithms 929 | 930 | Extending object interfaces: 931 | 932 | ```js 933 | interface A { 934 | a: string; 935 | } 936 | interface B extends A { 937 | b: (uint32) => uint32; 938 | } 939 | function f(c: B) { 940 | c.a = 'a'; 941 | c.b = b => b; 942 | } 943 | ``` 944 | 945 | Extending function interfaces: 946 | 947 | ```js 948 | interface A { 949 | (string); 950 | } 951 | interface B extends A { 952 | (string, string); 953 | } 954 | function f(a: B) { 955 | a('a'); 956 | a('a', 'b'); 957 | } 958 | ``` 959 | 960 | ### Implementing Interfaces 961 | - [ ] Proposal Specification Grammar 962 | - [ ] Proposal Specification Algorithms 963 | 964 | ```js 965 | interface A { 966 | a: uint32; 967 | b(uint32): uint32; 968 | } 969 | class B { 970 | } 971 | class C extends B implements A { 972 | b(a) { 973 | return a; 974 | } 975 | } 976 | const a = new C(); 977 | a.a = a.b(5); 978 | ``` 979 | 980 | Note that since ```b``` isn't overloaded, defining the type of the member function ```b``` in the class ```C``` isn't necessary. 981 | 982 | Once a class implements an interface it cannot remove that contract. Attempting to delete the member ```a``` or the method ```b``` would throw a TypeError. 983 | 984 | ### Typed Assignment 985 | - [ ] Proposal Specification Grammar 986 | - [ ] Proposal Specification Algorithms 987 | 988 | A variable by default is typed ```any``` meaning it's dynamic and its type changes depending on the last assigned value. As an example one can write: 989 | 990 | ```js 991 | let a = new MyType(); 992 | a = 5; // a is type any and is 5 993 | ``` 994 | If one wants to constrain the variable type they can write: 995 | ```js 996 | let a:MyType = new MyType(); 997 | // a = 5; // Equivelant to using implicit casting: a = MyType(5); 998 | ``` 999 | 1000 | This redundancy in declaring types for the variable can be removed with a typed assignment: 1001 | 1002 | ```js 1003 | let a := new MyType(); // a is type MyType 1004 | // a = 5; // Equivelant to using implicit casting: a = MyType(5); 1005 | ``` 1006 | 1007 | This new form of assignment is useful with both ```var``` and ```let``` declarations. With ```const``` it has no uses: 1008 | 1009 | ```js 1010 | const a = new MyType(); // a is type MyType 1011 | const b: MyType = new MyType(); // Redundant, b is type MyType even without explicitly specifying the type 1012 | const c := new MyType(); // Redundant, c is type MyType even without explicitly specifying the type 1013 | const d: MyType = 1; // Calls a matching constructor 1014 | const e: uint8 = 1; // Without the type this would have been typed Number 1015 | class A {} 1016 | class B extends A {} 1017 | const f: A = new B(); // This might not even be useful to allow 1018 | ``` 1019 | 1020 | This assignment also works with destructuring: 1021 | 1022 | ```js 1023 | let { a, b } := { (a: uint8): 1, (b: uint32): 2 }; // a is type uint8 and b is type uint32 1024 | ``` 1025 | 1026 | ### Function Overloading 1027 | - [ ] Proposal Specification Algorithms 1028 | 1029 | All function can be overloaded if the signature is non-ambiguous. A signature is defined by the parameter types and return type. (Return type overloading is covered in a subsection below as this is rare). 1030 | 1031 | ```js 1032 | function f(x: [].): string { return 'int32'; } 1033 | function f(s: [].): string { return 'string'; } 1034 | f(['test']); // "string" 1035 | ``` 1036 | 1037 | Up for debate is if accessing the separate functions is required. Functions are objects so using a key syntax with a string isn't ideal. Something like ```F['(int32[])']``` wouldn't be viable. It's possible ```Reflect``` could have something added to it to allow access. 1038 | 1039 | Signatures must match for a typed function: 1040 | ```js 1041 | function f(a: uint8, b: string) {} 1042 | // f(1); // TypeError: Function F has no matching signature 1043 | ``` 1044 | 1045 | Adding a normal untyped function acts like a catch all for any arguments: 1046 | 1047 | ```js 1048 | function f() {} // untyped function 1049 | function f(a: uint8) {} 1050 | f(1, 2); // Calls the untyped function 1051 | ``` 1052 | 1053 | If the intention is to created a typed function with no arguments then setting the return value is sufficient: 1054 | 1055 | ```js 1056 | function f(): void {} 1057 | // f(1); // TypeError: Function F has no matching signature 1058 | ``` 1059 | 1060 | Duplicate signatures are not allowed: 1061 | ```js 1062 | function f(a:uint8) {} 1063 | // function f(a: uint8, b: string = 'b') {} // TypeError: A function declaration with that signature already exists 1064 | f(8); 1065 | ``` 1066 | 1067 | Be aware that rest parameters can create identical signatures also. 1068 | ```js 1069 | function f(a: float32): void {} 1070 | // function f(...a: [].): void {} // TypeError: A function declaration with that signature already exists 1071 | ``` 1072 | 1073 | See the [Type Records](typerecords.md) page for more information on signatures. 1074 | 1075 | #### Overloading on Return Type 1076 | 1077 | ```js 1078 | function f(): uint32 { 1079 | return 10; 1080 | } 1081 | function f(): string { 1082 | return "10"; 1083 | } 1084 | // f(); // TypeError: Ambiguous signature for F. Requires explicit left-hand side type or cast. 1085 | const a: string = f(); // "10" 1086 | const b: uint32 = f(); // 10 1087 | 1088 | function g(a:uint32):uint32 { 1089 | return a; 1090 | } 1091 | g(f()); // 10 1092 | 1093 | function h(a:uint8) {} 1094 | function h(a:string) {} 1095 | // h(f()); // TypeError: Ambiguous signature for F. Requires explicit left-hand side type or cast. 1096 | h(uint32(f())); 1097 | ``` 1098 | 1099 | Overloading return types is especially useful on operators. Take SIMD operators, represented here by their intrinsic, that can return both a vector register or mask: 1100 | 1101 | ``` 1102 | __m128i _mm_cmpeq_epi32 (__m128i a, __m128i b) 1103 | __mmask8 _mm_cmpeq_epi32_mask (__m128i a, __m128i b) 1104 | ``` 1105 | Notice Intel differentiates signatures by adding ```_mask```. When translated to real types with operators they are identical however: 1106 | 1107 | ```js 1108 | //const something = int32x4(0, 1, 2, 3) === int32x4(0, 1, 3, 2); // TypeError: Ambiguous return type. Requires explicit cast to int32x4 or boolean8 1109 | ``` 1110 | With overloaded return types we can support both signatures: 1111 | ```js 1112 | const a: int32x4 = int32x4(0, 1, 2, 3) === int32x4(0, 1, 3, 2); 1113 | const b: boolean8 = int32x4(0, 1, 2, 3) === int32x4(0, 1, 3, 2); 1114 | ``` 1115 | For reference, the operators look like: 1116 | ```js 1117 | operator<(v: int32x4): int32x4 {} 1118 | operator<(v: int32x4): boolean8 {} 1119 | ``` 1120 | 1121 | ### Typed Promises 1122 | 1123 | Typed promises use a generic syntax where the resolve and reject type default to any. 1124 | 1125 | ```js 1126 | Promise 1127 | ``` 1128 | 1129 | ```js 1130 | const a = new Promise.((resolve, reject) => { 1131 | resolve(0); // or throw new Error(); 1132 | }); 1133 | ``` 1134 | To keep things consistent, the async version has the same return type. 1135 | ```js 1136 | async function f(): Promise. { 1137 | return 0; 1138 | } 1139 | ``` 1140 | If a Promise never throws anything then the following can be used: 1141 | 1142 | ```js 1143 | async function f(): Promise. { 1144 | return 0; 1145 | } 1146 | ``` 1147 | 1148 | Right now there's no check except the runtime check when a function actually throws to validate the exception types. It is feasible however that the immediate async function scope could be checked to match the type and generate a TypeError if one is found even for codepaths that can't resolve. This is stuff one's IDE might flag. 1149 | 1150 | #### Overloading Async Functions and Typed Promises 1151 | 1152 | While ```async``` functions and synchronous functions can overload the same name, they must have unique signatures. 1153 | 1154 | ```js 1155 | async function f(): Promise. {} 1156 | /* function f(): Promise. { // TypeError: A function with that signature already exists 1157 | return new Promise.((resolve, reject) => {}); 1158 | } */ 1159 | await f(); 1160 | ``` 1161 | 1162 | Refer to the try catch section on how different exception types would be explicitly captured: https://github.com/sirisian/ecmascript-types#try-catch 1163 | 1164 | ### Generator Overloading 1165 | - [ ] Proposal Specification Algorithms 1166 | 1167 | WIP: I don't like this syntax. 1168 | 1169 | ```js 1170 | var o = {}; 1171 | o[Symbol.iterator] = 1172 | [ 1173 | function* (): int32 { 1174 | yield* [1, 2, 3]; 1175 | }, 1176 | function* (): [int32, int32] { 1177 | yield* [[0, 1], [1, 2], [2, 3]]; 1178 | } 1179 | ]; 1180 | 1181 | [...o:int32]; // [1, 2, 3] Explicit selection of the generator return signature 1182 | for (const a:int32 of o) {} // Type is optional in this case 1183 | [...o:[int32, int32]]; // [[0, 1], [1, 2], [2, 3]] 1184 | for (const [a:int32, b:int32] of o) {} // Type is optional in this case 1185 | ``` 1186 | 1187 | I'd rather do something like: 1188 | 1189 | ```js 1190 | *operator...(): int32 { 1191 | yield* [1, 2, 3]; 1192 | } 1193 | *operator...(): [int32, int32] { 1194 | yield* [[0, 1], [1, 2], [2, 3]]; 1195 | } 1196 | ``` 1197 | 1198 | ### Object Typing 1199 | - [x] Proposal Specification Grammar 1200 | - [ ] Proposal Specification Algorithms 1201 | 1202 | Syntax: 1203 | 1204 | ```js 1205 | let o = { (a: uint8): 1 }; 1206 | ``` 1207 | This syntax is used because like destructuring the grammar cannot differentiate the multiple cases where types are included or excluded resulting in an ambiguous grammar. The parenthesis cleanly solves this. 1208 | 1209 | ```js 1210 | let a = []; 1211 | let o = { a }; 1212 | o = { a: [] }; 1213 | o = { (a: [].) }; // cast a to []. 1214 | o = { (a: [].):[] }; // new object with property a set to an empty array of type uint8[] 1215 | ``` 1216 | 1217 | This syntax works with any arrays: 1218 | 1219 | ```js 1220 | let o = { a: [] }; // Normal array syntax works as expected 1221 | let o = { (a: []): [] }; // With typing this is identical to the above 1222 | ``` 1223 | 1224 | ```Object.defineProperty``` and ```Object.defineProperties``` have a ```type``` key in the descriptor that accepts a type or string representing a type: 1225 | 1226 | ```js 1227 | Object.defineProperty(o, 'a', { type: uint8 }); // using the type 1228 | Object.defineProperty(o, 'b', { type: 'uint8' }); // using a string representing the type 1229 | ``` 1230 | 1231 | ```js 1232 | Object.defineProperties(o, { 1233 | 'a': { 1234 | type: uint8, 1235 | value: 0, 1236 | writable: true 1237 | }, 1238 | 'b': { 1239 | type: string, 1240 | value: 'a', 1241 | writable: true 1242 | } 1243 | }); 1244 | ``` 1245 | 1246 | The type information is also available in the property descriptor accessed with ```Object.getOwnPropertyDescriptor``` or ```Object.getOwnPropertyDescriptors```: 1247 | 1248 | ```js 1249 | const o = { a: uint8 }; 1250 | const descriptor = Object.getOwnPropertyDescriptor(o, 'a'); 1251 | descriptor.type; // uint8 1252 | 1253 | const descriptors = Object.getOwnPropertyDescriptors(o); 1254 | descriptors.a.type; // uint8 1255 | ``` 1256 | 1257 | Note that the ```type``` key in the descriptor is the actual type and not a string. 1258 | 1259 | The key ```value``` for a property with a numeric type defined in this spec defaults to 0. This modifies the behavior that currently says that ```value``` is defaulted to undefined. It will still be undefined if no ```type``` is set in the descriptor. The SIMD types also default to 0 and string defaults to an empty string. 1260 | 1261 | ### Class: Value Type and Reference Type Behavior 1262 | 1263 | Any class where at least one public and private field is typed is automatically sealed. (As if Object.seal was called on it). A frozen Object prototype is used as well preventing any modification except writing to fields. 1264 | 1265 | If every field is typed with a value type then instances can be treated like a value type in arrays. The class also inherits from SharedArrayBuffer allowing instances or arrays to be shared among web workers. 1266 | 1267 | ```js 1268 | class A { // can be treated like a value type 1269 | a: uint8; 1270 | #b: uint8; 1271 | } 1272 | class B extends A { // can be treated like a value type 1273 | a: uint16; 1274 | } 1275 | class C { // cannot be treated like a value type 1276 | a: uint8; 1277 | b; 1278 | } 1279 | ``` 1280 | 1281 | The value type behavior is used when creating sequential data in typed arrays. 1282 | 1283 | ```js 1284 | const a: [10].; // creates an array of 10 items with sequential data 1285 | a[0] = 10; 1286 | const b: [10].|null; // reference 1287 | b = a; 1288 | b[0]; // 10 1289 | ``` 1290 | This is identical to allocating an array of 20 bytes that looks like ```a, #b, a, #b, ...```. 1291 | 1292 | An array view can be created over this sequential memory to view as something else. Since this applies to all typed arrays, value type class array views can also be applied over contiguous bytes to create more readable code when parsing binary formats. 1293 | 1294 | ```js 1295 | class HeaderSection { 1296 | a: uint8; 1297 | b: uint32; 1298 | } 1299 | class Header { 1300 | a: uint8; 1301 | b: uint16; 1302 | c: HeaderSection; 1303 | } 1304 | const buffer: [100].; // Pretend this has data 1305 | const header = ref [].
(buffer)[0]; // Create a view over the bytes using the [].
and get the first element 1306 | header.c.a = 10; 1307 | buffer[3]; // 10 1308 | ``` 1309 | 1310 | When using value type classes in typed arrays it's beneficial to be able to reference individual elements. The example above uses this syntax. Refer to the references section on the syntax for this. Attempting to assign a value type to a variable would copy it creating a new instance. 1311 | 1312 | ```js 1313 | const header = [].
(buffer)[0]; 1314 | header.c.a = 10; 1315 | buffer[3]; // 0 1316 | ``` 1317 | 1318 | To create arrays of references simply union with null. 1319 | ```js 1320 | const a: [10].; // [null, ...] 1321 | a[0] = new A(); 1322 | ``` 1323 | 1324 | To change a class to be unsealed when its fields are typed use the ```dynamic``` keyword. This stops the class from being used for sequential data as well, so it cannot become a value type in typed arrays. 1325 | ```js 1326 | dynamic class A { 1327 | a: uint8; 1328 | #b: uint8; 1329 | } 1330 | const a: [10].; // [A, ...] 1331 | const b: [10].; // [null, ...] 1332 | ``` 1333 | 1334 | ### Constructor Overloading 1335 | - [ ] Proposal Specification Grammar 1336 | - [ ] Proposal Specification Algorithms 1337 | 1338 | ```js 1339 | class MyType { 1340 | x: float32; // Able to define members outside of the constructor 1341 | constructor(x: float32) { 1342 | this.x = x; 1343 | } 1344 | constructor(y: uint32) { 1345 | this.x = (y as float32) * 2; 1346 | } 1347 | } 1348 | ``` 1349 | 1350 | Implicit casting using the constructors: 1351 | ```js 1352 | let t: MyType = 1; // float32 constructor call 1353 | let t: MyType = 1 as uint32; // uint32 constructor called 1354 | ``` 1355 | 1356 | Constructing arrays all of the same type: 1357 | ```js 1358 | let t = new [5].(1); 1359 | ``` 1360 | 1361 | ### parseFloat and parseInt For Each New Type 1362 | - [ ] In Proposal Specification 1363 | - [ ] Proposal Specification Algorithms 1364 | 1365 | For integers (including ```bigint```) the parse function would have the signature ```parse(string, radix = 10)```. 1366 | 1367 | ```js 1368 | let a: uint8 = uint8.parse('1', 10); 1369 | let b: uint8 = uint8.parse('1'); // Same as the above with a default 10 for radix 1370 | let c: uint8 = '1'; // Calls parse automatically making it identical to the above 1371 | ``` 1372 | 1373 | For floats, decimals, and rational the signature is just ```parse(string)```. 1374 | 1375 | ```js 1376 | let a: float32 = float32.parse('1.2'); 1377 | ``` 1378 | 1379 | TODO: Define the expected inputs allowed. (See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). Also should a failure throw or return NaN if the type supports it. I'm leaning toward throwing in all cases where erroneous values are parsed. It's usually not in the program's design that NaN is an expected value and parsing to NaN just created hidden bugs. 1380 | 1381 | ### Implicit SIMD Constructors 1382 | - [ ] In Proposal Specification 1383 | - [ ] Proposal Specification Algorithms 1384 | 1385 | Going from a scalar to a vector: 1386 | 1387 | ```js 1388 | let a: float32x4 = 1; // Equivalent to let a = float32x4(1, 1, 1, 1); 1389 | ``` 1390 | 1391 | ### Classes and Operator Overloading 1392 | - [x] In Proposal Specification 1393 | - [x] [Proposal Specification Grammar](https://sirisian.github.io/ecmascript-types/#prod-MethodDefinition) 1394 | - [ ] Proposal Specification Algorithms 1395 | 1396 | A compact syntax is proposed with signatures. These can be overloaded to work with various types. Note that the unary operators have no parameters which differentiates them from the binary operators. 1397 | 1398 | See this for more examples: https://github.com/tc39/proposal-operator-overloading/issues/29 1399 | 1400 | ```js 1401 | class A { 1402 | operator+=(rhs) {} 1403 | operator-=(rhs) {} 1404 | operator*=(rhs) {} 1405 | operator/=(rhs) {} 1406 | operator%=(rhs) {} 1407 | operator**=(rhs) {} 1408 | operator<<=(rhs) {} 1409 | operator>>=(rhs) {} 1410 | operator>>>=(rhs) {} 1411 | operator&=(rhs) {} 1412 | operator^=(rhs) {} 1413 | operator|=(rhs) {} 1414 | operator+(rhs) {} 1415 | operator-(rhs) {} 1416 | operator*(rhs) {} 1417 | operator/(rhs) {} 1418 | operator%(rhs) {} 1419 | operator**(rhs) {} 1420 | operator<<(rhs) {} 1421 | operator>>(rhs) {} 1422 | operator>>>(rhs) {} 1423 | operator&(rhs) {} 1424 | operator|(rhs) {} 1425 | operator^(rhs) {} 1426 | operator~() {} 1427 | operator==(rhs) {} 1428 | operator!=(rhs) {} 1429 | operator<(rhs) {} 1430 | operator<=(rhs) {} 1431 | operator>(rhs) {} 1432 | operator>=(rhs) {} 1433 | operator&&(rhs) {} 1434 | operator||(rhs) {} 1435 | operator!() {} 1436 | operator++() {} // prefix (++a) 1437 | operator++(nothing) {} // postfix (a++) 1438 | operator--() {} // prefix (--a) 1439 | operator--(nothing) {} // postfix (a--) 1440 | operator-() {} 1441 | operator+() {} 1442 | get operator[]() {} 1443 | set operator[](...args, value) {} 1444 | operator T() {} // Implicit cast operator 1445 | } 1446 | ``` 1447 | 1448 | Examples: 1449 | 1450 | ```js 1451 | class Vector2 { 1452 | x: float32; 1453 | y: float32; 1454 | constructor(x: float32 = 0, y: float32 = 0) { 1455 | this.x = x; 1456 | this.y = y; 1457 | } 1458 | length(): float32 { 1459 | return Math.hypot(this.x, this.y); // uses Math.hypot(...:float32):float32 due to input and return type 1460 | } 1461 | operator+(v: Vector2): Vector2 { // Same as [Symbol.addition](v:Vector2) 1462 | return new vector2(this.x + v.x, this.y + v.y); 1463 | } 1464 | operator==(v: Vector2): boolean { 1465 | const epsilon = 0.0001; 1466 | return Math.abs(this.x - v.x) < epsilon && Math.abs(this.y - v.y) < epsilon; 1467 | } 1468 | } 1469 | 1470 | const a = new Vector2(1, 0); 1471 | const b = new Vector2(2, 0); 1472 | const c = a + b; 1473 | c.x; // 3 1474 | ``` 1475 | 1476 | Again this might not be viable syntax as it dynamically adds an operator and would incur performance issues: 1477 | ```js 1478 | var a = { b: 0 }; 1479 | a[Symbol.additionAssignment] = function(value) { 1480 | this.b += value; 1481 | }; 1482 | a += 5; 1483 | a.b; // 5 1484 | ``` 1485 | 1486 | #### Static Operator Overloading 1487 | 1488 | Classes can also implement static operator overloading. 1489 | 1490 | ```js 1491 | class A { 1492 | static x = 0; 1493 | static operator+=(value) { 1494 | this.x += value; 1495 | } 1496 | } 1497 | A += 5; // A.x is 5 1498 | ``` 1499 | 1500 | This is kind of niche, but it's consistent with other method definitions, so it's included. 1501 | 1502 | ### Class Extension 1503 | - [ ] Proposal Specification Algorithms 1504 | 1505 | Example defined in say ```MyClass.js``` defining extensions to ```Vector2``` defined above: 1506 | 1507 | ```js 1508 | class Vector2 { 1509 | operator==(v: MyClass) { 1510 | // equality check between this and MyClass 1511 | } 1512 | operator+(v: MyClass) { 1513 | return v + this; // defined in terms of the MyClass operator 1514 | } 1515 | } 1516 | ``` 1517 | 1518 | Note that no members may be defined in an extension class. The new methods are simply appended to the existing class definition. 1519 | 1520 | ### SIMD Operators 1521 | - [ ] In Proposal Specification 1522 | - [ ] Proposal Specification Algorithms 1523 | 1524 | All SIMD types would have operator overloading added when used with the same type. 1525 | ```js 1526 | let a = uint32x4(1, 2, 3, 4) + uint32x4(5, 6, 7, 8); // uint32x4 1527 | let b = uint32x4(1, 2, 3, 4) < uint32x4(5, 6, 7, 8); // boolean32x4 1528 | ``` 1529 | It's also possible to overload class operators to work with them, but the optimizations would be implementation specific if they result in SIMD instructions. 1530 | 1531 | ### enum Type 1532 | - [x] In Proposal Specification 1533 | - [x] Proposal Specification Grammar 1534 | - [ ] Proposal Specification Algorithms 1535 | 1536 | Enumerations with ```enum``` that support any type including functions and symbols. 1537 | ```js 1538 | enum Count { Zero, One, Two }; // Starts at 0 1539 | let c: Count = Count.Zero; 1540 | 1541 | enum Count { One = 1, Two, Three }; // Two is 2 since these are sequential 1542 | let c: Count = Count.One; 1543 | 1544 | enum Count: float32 { Zero, One, Two }; 1545 | 1546 | enum Counter: (float32) => float32 { Zero = x => 0, One = x => x + 1, Two = x => x + 2 } 1547 | ``` 1548 | 1549 | Custom sequential functions for types can be used. (Note these aren't closures): 1550 | ```js 1551 | enum Count: float32 { Zero = (index, name) => index * 100, One, Two }; // 0, 100, 200 1552 | enum Count: string { Zero = (index, name) => name, One, Two = (index, name) => name.toLowerCase(), Three }; // "Zero", "One", "two", "three" 1553 | enum Flags: uint32 { None = 0, Flag1 = (index, name) => 1 << (index - 1), Flag2, Flag3 } // 0, 1, 2, 4 1554 | ``` 1555 | An enumeration that uses a non-numeric type must define a starting value. If a sequential function or an overloaded assignment operator is not found the next value will be equal to the previous value. 1556 | 1557 | ```js 1558 | // enum Count:string { Zero, One, Two }; // TypeError Zero is undefined, expected string 1559 | enum Count:string { Zero = '0', One, Two }; // One and Two are also '0' because string has no prefix increment operator 1560 | ``` 1561 | 1562 | ```js 1563 | class A { 1564 | constructor(value) { 1565 | this.value = value; 1566 | } 1567 | operator+(value: number) { // prefix increment 1568 | return new A(this.value + value); 1569 | } 1570 | } 1571 | enum ExampleA: A { Zero = new A(0), One, Two }; // One = new A(0) + 1, Two = One + 1 using the addition operator. 1572 | ``` 1573 | 1574 | Index operator: 1575 | ```js 1576 | enum Count { Zero, One, Two }; 1577 | Count[0]; // Count.Zero 1578 | Count['Zero']; // Count.Zero 1579 | ``` 1580 | 1581 | Get ```enum``` value as string: 1582 | 1583 | ```js 1584 | Count.toString(Count.Zero); // 'Zero' 1585 | ``` 1586 | 1587 | It seems like there needs to be an expression form also. Something akin to Function or GeneratorFunction which allows the construction of features with strings. It's not clear to me if this is required or beneficial, but it could be. I guess the syntax would look like: 1588 | 1589 | ```js 1590 | new enum('a', 0, 'b', 1); 1591 | new enum(':uint8', 'a', 0, 'b', 1); 1592 | new enum(':string', 'None', 'none', 'Flag1', '(index, name) => name', 'Flag2', 'Flag3'); // This doesn't make much sense though since the value pairing is broken. Need a different syntax 1593 | ``` 1594 | 1595 | Similar to ```Array``` there would be a number of reserved functions: 1596 | 1597 | ```js 1598 | enum.prototype.keys() // Array Iterator with the string keys 1599 | enum.prototype.values() // Array Iterator with the values 1600 | enum.prototype.entries() // Array Iterator with [key, value] 1601 | enum.prototype.forEach((key, value, enumeration) => {}) 1602 | enum.prototype.filter((key, value, enumeration) => {}) // returns an Array 1603 | enum.prototype.map((key, value, enumeration) => {}) // returns an Array 1604 | enum.prototype[@@iterator]() 1605 | ``` 1606 | 1607 | Iteration would work like this: 1608 | 1609 | ```js 1610 | enum Count { Zero, One, Two }; 1611 | 1612 | for (const [key, value] of Count) { 1613 | // key = 'Zero', value = 0 1614 | } 1615 | ``` 1616 | 1617 | Enum values can reference previous values: 1618 | 1619 | ```js 1620 | enum E { A = 0, B = A + 5 }; 1621 | ``` 1622 | 1623 | ### Named Parameters 1624 | 1625 | Named parameters are a compact way to skip default parameters. 1626 | 1627 | ```js 1628 | function f(a: uint8, b: string = 0, ...args: string) {} 1629 | f(8, args: 'a', 'b'); 1630 | 1631 | function g(option1: string, option2: string) {} 1632 | g(option2: 'a'); // TypeError no signature for G matches (option2: string) 1633 | ``` 1634 | 1635 | Spread operator on an object will implement an iterable: 1636 | ```js 1637 | function f(a: uint32, b: string) {} 1638 | f(...{ a: 10, b: 'b' }); 1639 | ``` 1640 | 1641 | TODO: Syntax for adding parameters using spread? 1642 | 1643 | ```js 1644 | interface Config { 1645 | name: string, 1646 | min: uint32, 1647 | max: uint32 1648 | } 1649 | 1650 | function f(...{ ...Config }) { 1651 | console.log(name, min, max); 1652 | } 1653 | ``` 1654 | 1655 | TODO: Does this work with intersection and union types? 1656 | 1657 | ```js 1658 | type FloatType = { type: 'float', min: float32, max: float32 }; 1659 | type IntType = { type: 'int', min: int32, max: int32 }; 1660 | type Shared = { label: string }; 1661 | type Mixed = (FloatType | IntType) & Shared; 1662 | 1663 | function f(...Mixed: mixed) { 1664 | // Do something with label 1665 | // ... 1666 | match (mixed) { 1667 | FloatType: // float handling 1668 | IntType: // int handling 1669 | } 1670 | } 1671 | ``` 1672 | 1673 | ### Rest Parameters 1674 | - [x] Proposal Specification Grammar 1675 | - [ ] Proposal Specification Algorithms 1676 | 1677 | ```js 1678 | function f(a: string, ...args: [].) {} 1679 | f('a', 0, 1, 2, 3); 1680 | ``` 1681 | Rest parameters are valid for signatures: 1682 | ```js 1683 | let a:(...: [].); 1684 | ``` 1685 | Multiple rest parameters can be used: 1686 | ```js 1687 | function f(a: string, ...args: []., ...args2: []., callback: ()) {} 1688 | f('a', 0, 1, 2, 'a', 'b', () => {}); 1689 | ``` 1690 | Dynamic types have less precedence than typed parameters: 1691 | ```js 1692 | function f(...args1, callback: (), ...args2, callback: ()) {} 1693 | f('a', 1, 1.0, () => {}, 'b', 2, 2.0, () => {}); 1694 | ``` 1695 | Rest array destructuring: 1696 | ```js 1697 | function f(...[a: uint8, b: uint8, c: uint8]) { 1698 | return a + b + c; 1699 | } 1700 | ``` 1701 | 1702 | The behavior of rest parameters can create confusing signatures. While these are allowed, they aren't recommedned. Arguments are taken by parameters greedily and given back to satisfy signatures. 1703 | ```js 1704 | function f(...a: []., ...b: []., c: uint32): void {} 1705 | f(0, 1, 2); // a: [0, 1], b: [], c: 2 1706 | f(a: 0, 1, 2, b: 3, 4, 5, 6); // a: [0, 1, 2], b: [3, 4, 5], c: 6 1707 | ``` 1708 | 1709 | ### Try Catch 1710 | - [ ] Proposal Specification Grammar 1711 | - [ ] Proposal Specification Algorithms 1712 | 1713 | Catch clauses can be typed allowing for minimal conditional catch clauses. 1714 | 1715 | ```js 1716 | try { 1717 | // Statement that throws 1718 | } catch (e: TypeError) { 1719 | // Statements to handle TypeError exceptions 1720 | } catch (e: RangeError) { 1721 | // Statements to handle RangeError exceptions 1722 | } catch (e: EvalError) { 1723 | // Statements to handle EvalError exceptions 1724 | } catch (e) { 1725 | // Statements to handle any unspecified exceptions 1726 | } 1727 | ``` 1728 | 1729 | ### Placement New 1730 | - [x] [Proposal Specification Grammar](https://sirisian.github.io/ecmascript-types/#prod-ArrayView) 1731 | - [ ] Proposal Specification Algorithms 1732 | 1733 | Arbitrary arrays can be allocated into using the placement new syntax. This works with both a single instance and array of instances. 1734 | 1735 | Single instance syntax: 1736 | ```js 1737 | // new(buffer [, byteOffset]) Type() 1738 | let a = new(buffer, byteOffset) Type(0); 1739 | ``` 1740 | 1741 | Array of instances syntax: 1742 | ```js 1743 | // new(buffer [, byteOffset [, byteElementLength]]) [n].() 1744 | let a = new(buffer, byteOffset, byteElementLength) [10].(0); 1745 | ``` 1746 | 1747 | By default ```byteElementLength``` is the size of the type. Using a larger value than the size of the type acts as a stride adding padding between allocations in the buffer. Using a smaller length is unusual as it causes allocations to overlap. 1748 | 1749 | ### Value Type References 1750 | 1751 | https://github.com/rbuckton/proposal-refs 1752 | 1753 | The only difference with the above is that reference objects have operator overloading so there's no exposed ```value```. 1754 | 1755 | ```js 1756 | function f(ref a: int32) { 1757 | a++; 1758 | } 1759 | let a = 0; 1760 | f(ref a); 1761 | a; // 1 1762 | ``` 1763 | 1764 | If a property is in an object this can also be concise: 1765 | 1766 | ```js 1767 | const o = { a: 0 }; 1768 | f(ref o.a); 1769 | o.a; // 1 1770 | ``` 1771 | 1772 | Destructuring syntax supports references as well: 1773 | ```js 1774 | function f({ (ref a: int32) }) { 1775 | a++; 1776 | } 1777 | const o = { a: 0 }; 1778 | f(ref o); 1779 | o.a; // 1 1780 | ``` 1781 | 1782 | References can also be used to refer to elements in value type arrays. 1783 | 1784 | ```js 1785 | const a: [].; 1786 | let b = ref a[0]; 1787 | b = 10; 1788 | a[0]; // 10 1789 | ``` 1790 | 1791 | This works on value type classes described in another section. 1792 | 1793 | ```js 1794 | class A { 1795 | a:uint32; 1796 | b:uint32; 1797 | } 1798 | const a: [10].; 1799 | const b = ref a[0]; 1800 | b.a = 10; 1801 | 1802 | function f(ref c: A) { 1803 | c.a = 10; 1804 | } 1805 | f(ref a[1]); 1806 | ``` 1807 | 1808 | Functions can return a reference to an array value as well. 1809 | ```js 1810 | function f(a): int32 { 1811 | return ref a[0]; 1812 | } 1813 | const a: [10].; 1814 | f(a)++; // This is new syntax where the post-increment operates immediately on the returned value 1815 | a[0]; // 1 1816 | let ref b = f(a); 1817 | b = 10; 1818 | a[0]; // 10 1819 | ``` 1820 | 1821 | Reassigning a reference is allowed also: 1822 | ```js 1823 | const a: [10].; 1824 | let ref b = a[0]; 1825 | ref b = a[1]; 1826 | ``` 1827 | 1828 | ### Control Structures 1829 | - [ ] In Proposal Specification 1830 | - [ ] Proposal Specification Algorithms 1831 | 1832 | ## if else 1833 | 1834 | A table should be included here with every type and which values evaluate to executing. At first glance it might just be 0 and NaN do not execute and all other values do. SIMD types probably would not implicitly cast to boolean and attempting to would produce a TypeError indicating no implicit cast is available. 1835 | 1836 | ## switch 1837 | 1838 | The variable when typed in a switch statement must be integral, string, or symbol type. Specifically ```int8/16/32/64```, ```uint8/16/32/64```, ```number```, and ```string```. Most languages do not allow floating point case statements unless they also support ranges. (This could be considered later without causing backwards compatability issues). 1839 | 1840 | Enumerations can be used dependent on if their type is integral or string. 1841 | ```js 1842 | let a: uint32 = 10; 1843 | switch (a) { 1844 | case 10: 1845 | break; 1846 | case 'baz': // TypeError unexpected string literal, expected uint32 literal 1847 | break; 1848 | } 1849 | ``` 1850 | 1851 | ```js 1852 | let a: float32 = 1.23; 1853 | //switch (a) { // TypeError float32 is not a valid type for switch expression 1854 | //} 1855 | ``` 1856 | 1857 | ### Member memory alignment and offset 1858 | - [x] In Proposal Specification 1859 | - [ ] Proposal Specification Grammar 1860 | - [ ] Proposal Specification Algorithms 1861 | 1862 | By default the memory layout of a typed class - a class where every property is typed - simply appends to the memory of the extended class. For example: 1863 | 1864 | ```js 1865 | class A { 1866 | a: uint8; 1867 | } 1868 | class B extends A { 1869 | b: uint8; 1870 | } 1871 | // So the memory layout would be the same as: 1872 | class AB { 1873 | a: uint8; 1874 | b: uint8; 1875 | } 1876 | ``` 1877 | 1878 | Two new keys would be added to the property descriptor called ```align``` and ```offset```. For consistency between codebases two reserved decorators would be created called ```@align``` and ```@offset``` that would set the underlying keys with byte values. Align defines the memory address to be a multiple of a given number. (On some software architectures specialized move operations and cache boundaries can use these for small advantages). Offset is always defined as the number of bytes from the start of the class allocation in memory. (The offset starts at 0 for each class. Negative offset values can be used to overlap the memory of base classes). It's possible to create a union by defining overlapping offsets. 1879 | 1880 | Along with the member decorators, two object reserved descriptor keys would be created, ```alignAll``` and ```size```. These would control the allocated memory alignment of the instances and the allocated size of the instances. 1881 | 1882 | WIP: Need byte and bit versions of these alignment features. 1883 | 1884 | ```js 1885 | @alignAll(16) // Defines the class memory alignment to be 16 byte aligned 1886 | @size(32) // Defines the class as 32 bytes. Pads with zeros when allocating 1887 | class A { 1888 | @offset(2) 1889 | x: float32; // Aligned to 16 bytes because of the class alignment and offset by 2 bytes because of the property alignment 1890 | @align(4) 1891 | y: float32x4; // 2 (from the offset above) + 4 (for x) is 6 bytes and we said it has to be aligned to 4 bytes so 8 bytes offset from the start of the allocation. Instead of @align(4) we could have put @offset(8) 1892 | } 1893 | ``` 1894 | 1895 | The following is an example of overlapping properties using ```offset``` creating a union where both properties map to the same memory. Notice the use of a negative offset to reach into a base class memory. 1896 | 1897 | ```js 1898 | class A { 1899 | a: uint8; 1900 | } 1901 | class B extends A { 1902 | @offset(-1) 1903 | b: uint8; 1904 | } 1905 | // So the memory layout would be the same as: 1906 | class AB { // size is 1 byte 1907 | a: uint8; 1908 | @offset(0) 1909 | b: uint8; 1910 | } 1911 | const ab = new AB(); 1912 | ab.a = 10; 1913 | ab.b == 10; // true 1914 | ``` 1915 | 1916 | These descriptor features only apply if all the properties in a class are typed along with the complete prototype chain. 1917 | 1918 | WIP: Adding properties later with ```Object.defineProperty``` is only allowed on dynamic class instances. 1919 | 1920 | ```js 1921 | class A { 1922 | a: uint8; 1923 | constructor(a:uint8) { 1924 | this.a = a; 1925 | } 1926 | } 1927 | const a:[]. = [0, 1, 2]; 1928 | 1929 | Object.defineProperty(A, 'b', { 1930 | value: 0, 1931 | writable: true, 1932 | type: uint8 1933 | }); 1934 | const b:[]. = [0, 1, 2]; 1935 | 1936 | // a[0].b // TypeError: Undefined property b 1937 | b[0].b; // 0 1938 | ``` 1939 | 1940 | ### Global Objects 1941 | - [ ] In Proposal Specification 1942 | 1943 | The following global objects could be used as types: 1944 | 1945 | ```DataView```, ```Date```, ```Error```, ```EvalError```, ```InternalError```, ```Map```, ```Promise```, ```Proxy```, ```RangeError```, ```ReferenceError```, ```RegExp```, ```Set```, ```SyntaxError```, ```TypeError```, ```URIError```, ```WeakMap```, ```WeakSet``` 1946 | 1947 | ### Generics 1948 | 1949 | [Generics](generics.md) 1950 | 1951 | ### Decorators 1952 | 1953 | [Decorators](decorators.md) 1954 | 1955 | ### Records and Tuples 1956 | 1957 | https://github.com/sirisian/ecmascript-types/issues/56 1958 | 1959 | Types would work as expected with Records and Tuples: 1960 | ```js 1961 | interface IPoint { x: int32, y: int32 } 1962 | const ship1:IPoint = #{ x: 1, y: 2 }; 1963 | // ship2 is an ordinary object: 1964 | const ship2:IPoint = { x: -1, y: 3 }; 1965 | 1966 | function move(start: IPoint, deltaX: int32, deltaY: int32): IPoint { 1967 | // we always return a record after moving 1968 | return #{ 1969 | x: start.x + deltaX, 1970 | y: start.y + deltaY, 1971 | }; 1972 | } 1973 | 1974 | const ship1Moved = move(ship1, 1, 0); 1975 | // passing an ordinary object to move() still works: 1976 | const ship2Moved = move(ship2, 3, -1); 1977 | 1978 | console.log(ship1Moved === ship2Moved); // true 1979 | // ship1 and ship2 have the same coordinates after moving 1980 | ``` 1981 | 1982 | ```js 1983 | const measures = #[uint8(42), uint8(12), uint8(67), "measure error: foo happened"]; 1984 | 1985 | // Accessing indices like you would with arrays! 1986 | console.log(measures[0]); // 42 1987 | console.log(measures[3]); // measure error: foo happened 1988 | 1989 | // Slice and spread like arrays! 1990 | const correctedMeasures = #[ 1991 | ...measures.slice(0, measures.length - 1), 1992 | int32(-1) 1993 | ]; 1994 | console.log(correctedMeasures[0]); // 42 1995 | console.log(correctedMeasures[3]); // -1 1996 | 1997 | // or use the .with() shorthand for the same result: 1998 | const correctedMeasures2 = measures.with(3, -1); 1999 | console.log(correctedMeasures2[0]); // 42 2000 | console.log(correctedMeasures2[3]); // -1 2001 | 2002 | // Tuples support methods similar to Arrays 2003 | console.log(correctedMeasures2.map(x => x + 1)); // #[43, 13, 68, 0] 2004 | ``` 2005 | 2006 | ## Undecided Topics 2007 | 2008 | ### Import Types 2009 | - [ ] In Proposal Specification 2010 | 2011 | This has been brought up before, but possible solutions due to compatability issues would be to introduce special imports. Brenden once suggested something like: 2012 | ```js 2013 | import {int8, int16, int32, int64} from "@valueobjects"; 2014 | //import "@valueobjects"; 2015 | ``` 2016 | 2017 | ## Overview of Future Considerations and Concerns 2018 | 2019 | ### Partial Class 2020 | 2021 | ```js 2022 | partial class MyType { 2023 | } 2024 | ``` 2025 | 2026 | Partial classes are when you define a single class into multiple pieces. When using partial classes the ordering members would be undefined. What this means is you cannot create views of a partial class using the normal array syntax and this would throw an error. 2027 | 2028 | ### Switch ranges 2029 | 2030 | If case ranges were added and switches were allowed to use non-integral and non-string types then the following syntax could be used in future proposals without conflicting since this proposal would throw a ```TypeError``` restricting all cases of its usage keeping the behavior open for later ideas. 2031 | 2032 | ```js 2033 | let a:float32 = 1 / 5; 2034 | switch (a) { 2035 | case 0..0.99: 2036 | break; 2037 | } 2038 | ``` 2039 | 2040 | ### Bit-fields 2041 | 2042 | These are incredibly niche. That said I've had at least one person mention them to me in an issue. C itself has many rules like that property order is undefined allowing properties to be rearranged more optimally meaning the memory layout isn't defined. This would all have to be defined probably since some view this as a benefit and others view it as a design problem. Controlling options with decorators might be ideal. Packing and other rules would also need to be clearly defined. 2043 | 2044 | ```js 2045 | class Vector2 { 2046 | x: uint.<4>; // 4 bits 2047 | @offsetBit(4) 2048 | y: uint.<4>; // 4 bits 2049 | } 2050 | ``` 2051 | 2052 | ### Exception filters 2053 | 2054 | See https://github.com/sirisian/ecmascript-types/issues/22 2055 | 2056 | A very compact syntax can be used later for exception filters: 2057 | 2058 | ```js 2059 | catch (e: Error => e.message == 'a') 2060 | ``` 2061 | 2062 | Or 2063 | 2064 | ```js 2065 | catch (e: Error => console.log(e.message)) 2066 | ``` 2067 | 2068 | This accomplishes exception filters without requiring a keyword like "when". That said it would probably not be a true lambda and instead be limited to only expressions. 2069 | 2070 | ### Threading 2071 | 2072 | [Threading](threading.md) 2073 | 2074 | # Example: 2075 | Packet bit writer/reader https://gist.github.com/sirisian/dbc628dde19771b54dec 2076 | 2077 | # Previous discussions 2078 | 2079 | Current Mailing List Thread: https://esdiscuss.org/topic/proposal-optional-static-typing-part-3 2080 | 2081 | Second Thread: https://esdiscuss.org/topic/optional-static-typing-part-2 2082 | Original thread: https://esdiscuss.org/topic/es8-proposal-optional-static-typing 2083 | This one contains a lot of my old thoughts: https://esdiscuss.org/topic/proposal-for-new-floating-point-and-integer-data-types 2084 | https://esdiscuss.org/topic/optional-strong-typing 2085 | https://esdiscuss.org/topic/optional-argument-types 2086 | --------------------------------------------------------------------------------