├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Levent Arman Özak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript Questions 2 | 3 | Back in April and May 2023, I asked daily TypeScript questions on [my Twitter account](https://twitter.com/armanozak) for 30 working days. This repository is originally a compilation of these questions, but if you want to contribute, please feel free to create a PR. 4 | 5 | ## Q1 6 | 7 | ```typescript 8 | const foo = "1234"; 9 | ``` 10 | 11 | What is the type of `foo`? 12 | 13 |
14 | Answer 15 | 16 | `"1234"` 17 | 18 | TypeScript infers this type as a string literal and not as string because it is a constant. If we had used let, then the inferred type would have been string. 19 | 20 | Can we force TypeScript to use `string` here? Yes. 21 | 22 | ```typescript 23 | const foo: string = "1234" 24 | ``` 25 | 26 | Info: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types 27 | 28 |
29 | 30 | ## Q2 31 | 32 | ```typescript 33 | enum Flag { 34 | Foo, 35 | Bar = -2, 36 | Baz 37 | } 38 | ``` 39 | 40 | What are the values of `Foo` and `Baz` flags? 41 | 42 | > Note: This is just an example to test your knowledge. I recommend assigning all enum values explicitly, even when they are ordered. 43 | 44 |
45 | Answer 46 | 47 | **Foo = 0, Baz = -1** 48 | 49 | When an enum member doesn't have an initializer, it is assigned as previous member value +1 or 0 if it is the first member. 50 | 51 | Info: https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members 52 | 53 |
54 | 55 | ## Q3 56 | 57 | ```typescript 58 | const myObject = { 59 | id: crypto.randomUUID(), 60 | }; 61 | 62 | type Id = ; 63 | // ^ 64 | // What should I write here to create a type alias 65 | // for the id property of myObject? 66 | ``` 67 | 68 | I want to create a type alias for `myObject`'s `id` property. How should I fill in the blank to do this? 69 | 70 |
71 | Answer 72 | 73 | `typeof myObject.i​d` 74 | 75 | We want to keep the `Id` type alias compatible with the actual type of that property, so we can't; 76 | 77 | - use hard-coded types. 78 | - leverage any technique that gets the return type of randomUUID (because it can change too). 79 | 80 | Info: https://www.typescriptlang.org/docs/handbook/2/typeof-types.html 81 | 82 |
83 | 84 | ## Q4 85 | 86 | ```typescript 87 | function add(x: number, y: number) { 88 | return x + y; 89 | } 90 | 91 | const params = [1, 2]; 92 | 93 | add(...params); 94 | // ^ 95 | // I'm getting an error here. How can I make it go away? 96 | ``` 97 | 98 | I want to spread those parameters, but I'm getting an error. What should I do? 99 | 100 |
101 | Answer 102 | 103 | ```typescript 104 | const params: Parameters = [1, 2]; 105 | ``` 106 | 107 | Can't we use a tuple instead? Yes, assigning `[number, number]` to or using `as const` on params will work too. However, deriving the type from the parameters of the `add` function will always keep them in sync. 108 | 109 | Info: https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype 110 | 111 |
112 | 113 | ## Q5 114 | 115 | ```typescript 116 | interface Foo { 117 | x: number; 118 | y: number; 119 | } 120 | 121 | interface Bar { 122 | q: string; 123 | } 124 | 125 | let fooOrBar: Foo | Bar; 126 | 127 | fooOrBar = { q: "" }; 128 | type T1 = typeof fooOrBar; 129 | 130 | fooOrBar = { x: 0, y: 1 }; 131 | type T2 = typeof fooOrBar; 132 | ``` 133 | 134 | What are `T1` and `T2`? 135 | 136 |
137 | Answer 138 | 139 | **T1 = Bar, T2 = Foo** 140 | 141 | The type of `fooOrBar` is a union type (`Foo | Bar`). However, TypeScript is able to narrow types based on assignments. So, `T1` becomes Bar. Then we assign another value and TypeScript narrows the type again, this time as `Foo`. 142 | 143 | Info: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#assignments 144 | 145 |
146 | 147 | ## Q6 148 | 149 | ```typescript 150 | class SplitAfterEach { 151 | constructor(public readonly search: string) {} 152 | 153 | [Symbol.split](str: string) { 154 | const result: string[] = []; 155 | 156 | do { 157 | let index = str.indexOf(this.search); 158 | if (index < 0) { 159 | result.push(str); 160 | break; 161 | } 162 | 163 | index += this.search.length; 164 | result.push(str.substring(0, index)); 165 | str = str.substring(index); 166 | } while (str.length); 167 | 168 | return result; 169 | } 170 | } 171 | 172 | console.log("foobarfoobar".split(new SplitAfterEach("foo"))); 173 | 174 | type StringKeysOfSplitAfterEach = ; 175 | // ^ 176 | // How can I get only the string keys of SplitAfterEach? 177 | ``` 178 | 179 | How should I fill in the blank to get only the string keys of `SplitAfterEach`? 180 | 181 |
182 | Answer 183 | 184 | **Option 1:** `keyof SplitAfterEach & string` 185 | 186 | We can get keys with `keyof`, but keys can be of `number` or `symbol` type too. An intersection type narrows it for us. 187 | 188 | Info: https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types 189 | 190 | **Option 2:** Use a utility type 191 | 192 | `Extract` 193 | 194 | Info: https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union 195 | 196 |
197 | 198 | ## Q7 199 | 200 | ```typescript 201 | type X = Awaited>>; 202 | ``` 203 | 204 | What is `X`? 205 | 206 |
207 | Answer 208 | 209 | `string` 210 | 211 | `Awaited` is a utility type that unwraps promises recursively. 212 | 213 | Info: https://www.typescriptlang.org/docs/handbook/utility-types.html#awaitedtype 214 | 215 |
216 | 217 | ## Q8 218 | 219 | ```typescript 220 | type BuildEvent = "BuildError" | "BuildSuccess"; 221 | type HttpEvent = "HttpError" | "HttpSuccess"; 222 | type InitEvent = "InitError" | "InitSuccess"; 223 | type NavEvent = "NavError" | "NavSuccess"; 224 | 225 | type AppEvent = BuildEvent | HttpEvent | InitEvent | NavEvent; 226 | 227 | type AppError = Extract; 228 | // ^ 229 | // What should I write here to get all error types? 230 | // i.e. "BuildError" | "HttpError" | "InitError" | "NavError" 231 | ``` 232 | 233 | How should I fill in the blank to get all error types and none of the success types? 234 | 235 |
236 | Answer 237 | 238 | `` `${string}Error` `` 239 | 240 | Template literals allow us to compose string literal types, and string acts like a wildcard. So, when `Extract` asserts the assignability of the `AppEvent` union against `${string}Error`, all members ending with "Error" pass. 241 | 242 | Info: https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html 243 | 244 |
245 | 246 | ## Q9 247 | 248 | ```typescript 249 | type BuildEvent = "BuildError" | "BuildSuccess"; 250 | type HttpEvent = "HttpError" | "HttpSuccess"; 251 | type InitEvent = "InitError" | "InitSuccess"; 252 | type NavEvent = "NavError" | "NavSuccess"; 253 | 254 | type AppEvent = BuildEvent | HttpEvent | InitEvent | NavEvent; 255 | 256 | // This works. The type is a union of all errors. 257 | type AppError = Extract; 258 | 259 | // This doesn't! The type is never. 260 | type AppNever = AppEvent extends `${string}Error` ? AppEvent : never; 261 | 262 | // Why? 263 | ``` 264 | 265 | In the last question, we used `Extract` to get all error types. [`Extract` is a very simple generic type](https://github.com/microsoft/TypeScript/blob/main/src/lib/es5.d.ts): 266 | 267 | `type Extract = T extends U ? T : never;` 268 | 269 | However, when I implement the same conditional type directly, it doesn't work. Why? 270 | 271 |
272 | Answer 273 | 274 | Generic types distribute unions. 275 | 276 | Every member of a union is evaluated separately when passed to any generic type. So this works too: 277 | 278 | ```typescript 279 | type ExtractError = T extends `${string}Error` ? T : never; 280 | type AppError = ExtractError; 281 | ``` 282 | 283 | Info: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types 284 | 285 |
286 | 287 | ## Q10 288 | 289 | ```typescript 290 | // human.ts 291 | export class Human { 292 | constructor(public readonly name: string) {} 293 | 294 | breath() { 295 | console.log(`${this.name} breaths`); 296 | } 297 | 298 | eat(food: string) { 299 | console.log(`${this.name} eats ${food}`); 300 | } 301 | 302 | sleep() { 303 | console.log(`${this.name} sleeps`); 304 | } 305 | 306 | walk() { 307 | console.log(`${this.name} walks`); 308 | } 309 | } 310 | ``` 311 | 312 | ```typescript 313 | // speech.ts 314 | import { Human } from "./human"; 315 | 316 | Human.prototype.speak = function (this: Human, sth: string) { 317 | // ^ 318 | // I'm getting an error: Property 'speak' does not exist on type 'Human'. 319 | 320 | console.log(`${this.name} says "${sth}"`); 321 | }; 322 | 323 | 324 | // How can I add the "speak" method the "Human" interface? 325 | ``` 326 | 327 | ```typescript 328 | // index.ts 329 | import { Human } from "./human"; 330 | import "./speech"; 331 | 332 | const human = new Human("Guru"); 333 | 334 | human.speak("Hello world!"); 335 | ``` 336 | 337 | How can I add a `speak` method to the `Human` interface using the _speech_ module? 338 | 339 |
340 | Answer 341 | 342 | With module augmentation. 343 | 344 | Placing the code below in _speech.ts_ will add the speak method to the `Human` interface: 345 | 346 | ```typescript 347 | declare module './human' { 348 | interface Human { 349 | speak(str: string): void; 350 | } 351 | } 352 | ``` 353 | 354 | Info: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation 355 | 356 |
357 | 358 | ## Q11 359 | 360 | ```typescript 361 | /* Note: This file is imported by the entry file. */ 362 | 363 | import * as MSW from "msw"; 364 | 365 | (global as any).msw = MSW; 366 | 367 | // How can I add "msw" to the global scope in TypeScript? 368 | ``` 369 | 370 | I want to use the MSW library in my test files without importing it in each file, so I mutated the global object. How can I add `msw` to the global scope in TypeScript too? 371 | 372 |
373 | Answer 374 | 375 | With global augmentation. 376 | 377 | Adding this to the _test-globals.ts_ file will declare `msw` as a global variable: 378 | 379 | ```typescript 380 | declare global { 381 | const msw: typeof MSW; 382 | } 383 | ``` 384 | 385 | Info: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation 386 | 387 |
388 | 389 | ## Q12 390 | 391 | ```typescript 392 | type MapLink = string; 393 | 394 | interface Address { 395 | street: string; 396 | no: string; 397 | postalCode: string; 398 | city: string; 399 | country: string; 400 | } 401 | 402 | interface Coordinates { 403 | x: number; 404 | y: number; 405 | } 406 | 407 | declare function getLinkForAddress(address: Address): MapLink; 408 | declare function getLinkForCoordinates(coordinates: Coordinates): MapLink; 409 | 410 | function getLink(addressOrCoordinates: Address | Coordinates) { 411 | if (isCoordinates(addressOrCoordinates)) { 412 | // ^ 413 | // This doesn't narrow the type to Coordinates and I get errors. 414 | return getLinkForCoordinates(addressOrCoordinates); 415 | } 416 | 417 | return getLinkForAddress(addressOrCoordinates); 418 | } 419 | 420 | // What should I add to isCoordinates function to narrow the type? 421 | function isCoordinates(input: object) { 422 | return "x" in input && "y" in input; 423 | } 424 | ``` 425 | 426 | I am getting errors when I pass `addressOrCoordinates` as an argument to `getLinkForCoordinates` and `getLinkForAddress` functions. What should I add to `isCoordinates` function to narrow the type? 427 | 428 |
429 | Answer 430 | 431 | `input is Coordinates` as return type 432 | 433 | ```typescript 434 | function isCoordinates( 435 | input: object 436 | ): input is Coordinates { 437 | return "x" in input && "y" in input; 438 | } 439 | ``` 440 | 441 | This kind of return type is called a "type predicate". 442 | 443 | Info: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates 444 | 445 |
446 | 447 | ## Q13 448 | 449 | ```typescript 450 | interface Petrol { 451 | engineType: "combustion"; 452 | fillTank(): void; 453 | } 454 | 455 | interface Diesel { 456 | engineType: "combustion"; 457 | fillTank(): void; 458 | } 459 | 460 | interface Electric { 461 | engineType: "electricity"; 462 | charge(): void; 463 | } 464 | 465 | interface Hybrid { 466 | engineType: "combustion & electricity"; 467 | fillTank(): void; 468 | charge(): void; 469 | } 470 | 471 | type CarType = Petrol | Diesel | Electric | Hybrid; 472 | 473 | function getArea(car: CarType) { 474 | switch (car.engineType) { 475 | case "combustion": 476 | return car.fillTank(); 477 | case "electricity": 478 | return car.charge(); 479 | default: 480 | return exhaustCases(car); 481 | // ^ 482 | // How can I declare this function so that I get a type error here? 483 | } 484 | } 485 | ``` 486 | 487 | I want to get a type error when not all cases are exhausted. How should I declare the `exhaustCases` function for that? Please use the `declare function ...` syntax. 488 | 489 |
490 | Answer 491 | 492 | ```typescript 493 | declare function exhaustCases( 494 | input: never 495 | ): never; 496 | ``` 497 | 498 | `never` is assignable to all types, but no other type is assignable to `never`. If the case clauses in your switch statement exhaust all members of the union type, TypeScript will narrow the type in the default clause down to `never`, so there will be no errors. In this example, the `Hybrid` interface is not exhausted, so we would get an error if we try to assign it to a parameter with `never` type. 499 | 500 | Info: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking 501 | 502 | > Why is the return type `never` and not `void`? 🤔 503 | > 504 | > Well, in this example, it is possible to return void. However, we would have to update it if we ever update the return types of `fillTank` and `charge` methods, otherwise we would end up with a union type for no reason. Take a look at this playground: https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgAoTFA9gG2QbwChlkIQBzUCAFQE8AHCALmQCIEsBbAIwFcBnMMCwhWAbmLIYwHDmpwQAawAUAShbcsuCAokBfQoVCRYiFABFgEfhDxESZSiBoNmbDjwFCR4ydNnySmoaWjg6IPqGxtDwSMgAomEImMAIBJKOVHSMLKy2EMlQqcBgtL4kCAAWcFDkEMHImtq6hAZG4DFmyAAStNxFACbpDhRZrrkefILCIMgAZKRJKQglZRIk-nIKKuqNoeHryFU1dQ1NYS1tpYzIAMI12SgAvGgY2HgAPsiW1rbIX4kCst-j0+oMJIQYLwQMkZsgoBAYAj+JV4s5arQAMpYXhQJDKBA1Fj3KCPVTIAD0ACo9s1Zl8AG5YYBDKkU4bIfgAdxKVWQBJqADpMs4yRyKnAbO4uFNvKImJISCQEWBcbNCVBBZtAjtDhKpXklkUVqVWAqlUqVWqjkLjrV6qo9cgBoi4LwcGBzRb4RhrRAAB7VLz3Gz8AVQR2SAxtF0IHA1FBQmFy0iBt2CEPWZSgei8T3IZwM6C7JkssRAA 505 | > 506 | > `never` as return type is the best option because it won't change the return type of the function and will always work. So, we can even have a common `exhaustCases` function that works for all switch statements. 507 | 508 |
509 | 510 | ## Q14 511 | 512 | ```typescript 513 | type Operation = "upsert" | "delete"; 514 | 515 | interface Options { 516 | skipErrors?: boolean; 517 | } 518 | 519 | // None of these should lead to type errors: 520 | execute("upsert", "foo"); 521 | execute("delete", "foo", { skipErrors: false }); 522 | execute("upsert", "foo", "bar"); 523 | execute("delete", "foo", "bar", { skipErrors: true }); 524 | execute("upsert", "foo", "bar", "baz"); 525 | execute("delete", "foo", "bar", "baz", {}); 526 | execute("upsert", ...(["foo", "bar"] as const)); 527 | execute("delete", ...(["foo", "bar"] as const), {}); 528 | execute("upsert", "x", ...["foo", "bar", "baz"]); 529 | execute("delete", "x", ...["foo", "bar", "baz"], {}); 530 | 531 | // All of these should lead to type errors: 532 | execute("upsert"); 533 | execute("delete", {}); 534 | execute("upsert", {}, "foo"); 535 | execute("delete", 1, "foo"); 536 | execute("upsert", "foo", 1, "bar"); 537 | execute("delete", "foo", { skipErrors: "true" }); 538 | execute("foo", "bar", "baz"); 539 | 540 | // How should I declare the execute function? 541 | // Note: Use void as return type. 542 | ``` 543 | 544 | How should I declare the `execute` function? You can use `void` as its return type. Please use the `declare function ...` syntax. 545 | 546 |
547 | Answer 548 | 549 | ```typescript 550 | declare function execute( 551 | operation: Operation, 552 | requiredParam: string, 553 | ...optionalParams: string[] | [...string[], Options] 554 | ): void; 555 | ``` 556 | 557 | Using an overload here is worse than a union type because of two reasons: 558 | 559 | - Instead of just "skipErrors", TypeScript auto-complete displays all string methods with an "s". 560 | - `execute("upsert", "foo", 1, "bar")` is completely highlighted as an error because it doesn't match any overloads, whereas with union types only the part that doesn't match the signature is highlighted. 561 | 562 | > Please try not to declare a function like that in your work. 😅 563 | 564 |
565 | 566 | ## Q15 567 | 568 | ```typescript 569 | type A = void extends true ? 1 : 0; 570 | type B = true extends void ? 1 : 0; 571 | type X = (() => void) extends (() => true) ? 1 : 0; 572 | type Y = (() => true) extends (() => void) ? 1 : 0; 573 | ``` 574 | 575 | What are `A`, `B`, `X`, and `Y`? 576 | 577 |
578 | Answer 579 | 580 | **A = 0, B = 0, X = 0, Y = 1** 581 | 582 | `void` and `true` don't extend each other, but `void` as a return type has a special status to allow this: 583 | 584 | ```typescript 585 | declare const records: string[]; 586 | declare function register(rec: string): boolean; 587 | records.forEach(register); 588 | ``` 589 | 590 | Info: https://www.typescriptlang.org/docs/handbook/2/functions.html#return-type-void 591 | 592 |
593 | 594 | ## Q16 595 | 596 | ```typescript 597 | class Point { 598 | get #self() { 599 | return this as Mutable; 600 | } 601 | 602 | constructor( 603 | public readonly x: number, 604 | public readonly y: number 605 | ) {} 606 | 607 | move(x: number, y: number) { 608 | this.#self.x += x; 609 | this.#self.y += y; 610 | } 611 | 612 | [Symbol.iterator]() { 613 | return [this.x, this.y][Symbol.iterator](); 614 | } 615 | } 616 | 617 | const point = new Point(0, 1); 618 | console.log(...point); // 0 1 619 | 620 | point.move(2, 3); 621 | console.log(...point); // 2 4 622 | ``` 623 | 624 | Please declare the `Mutable` type and make sure it is reusable. 625 | 626 |
627 | Answer 628 | 629 | ```typescript 630 | type Mutable = { 631 | -readonly [K in keyof T]: T[K]; 632 | }; 633 | ``` 634 | 635 | Removing modifiers like `readonly` and optional (`?`) is possible while mapping types. 636 | 637 | Info: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers 638 | 639 |
640 | 641 | ## Q17 642 | 643 | ```typescript 644 | interface Point { 645 | readonly x: number; 646 | readonly y: number; 647 | } 648 | 649 | type Mutable = Reveal< 650 | { 651 | -readonly [K in keyof Pick]: T[K]; 652 | } & Omit 653 | >; 654 | 655 | type HorizontallyMovablePoint = Mutable; 656 | // { 657 | // x: number; 658 | // readonly y: number; 659 | // } 660 | ``` 661 | 662 | Let's add the option to pick keys to the `Mutable` type in the previous question. What should the `Reveal` type be, if we want to see the whole interface and not some obscure intersection type on hover? 663 | 664 |
665 | Answer 666 | 667 | ```typescript 668 | type Reveal = { 669 | [K in keyof T]: T[K]; 670 | } & {}; 671 | ``` 672 | 673 |
674 | 675 | ## Q18 676 | 677 | ```typescript 678 | interface Point { 679 | [stringIndex: string]: any; 680 | [symbolIndex: symbol]: any; 681 | [templateLiteralIndex: `distanceTo${string}`]: number; 682 | name: string; 683 | readonly x: number; 684 | readonly y: number; 685 | move(x: number, y: number): void; 686 | } 687 | 688 | type BasePoint = OmitIndexSignatures; 689 | // type BasePoint = { 690 | // name: string; 691 | // readonly x: number; 692 | // readonly y: number; 693 | // move: (x: number, y: number) => void; 694 | // } 695 | ``` 696 | 697 | How should I declare the `OmitIndexSignatures` type so that `BasePoint` won't have any index signatures? 698 | 699 | Ref: https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures 700 | 701 |
702 | Answer 703 | 704 | ```typescript 705 | type OmitIndexSignatures = { 706 | [ 707 | K in keyof T 708 | as {} extends Record ? never : K 709 | ]: T[K]; 710 | }; 711 | ``` 712 | 713 | Credit: https://stackoverflow.com/questions/51465182/how-to-remove-index-signature-using-mapped-types/68261113#68261113 714 | 715 |
716 | 717 | ## Q19 718 | 719 | ```typescript 720 | type T0 = Await>; // boolean 721 | type T1 = Await>>; // number 722 | type T2 = Await>>>; // symbol 723 | type T3 = Await, 'then'>>; // string 724 | type T4 = Await<{then(mapFn: (url: URL) => T, flag: boolean): T}>; // URL 725 | type T5 = Await<{then(): Promise}>; // never 726 | type T6 = Await<{then: Event}>; // { then: Event } 727 | type T7 = Await; // Date 728 | type T8 = Await; // null 729 | type T9 = Await; // undefined 730 | ``` 731 | 732 | Please declare the `Await` type without using (or peeking at 🙂) the built-in `Awaited` type. You may assume strict mode. 733 | 734 |
735 | Answer 736 | 737 | ```typescript 738 | type Await = 739 | T extends { 740 | then: (fn: infer Fn, ...args: any[]) => any 741 | } 742 | ? Fn extends (value: infer V, ...args: any[]) => any 743 | ? Await 744 | : never 745 | : T; 746 | ``` 747 | 748 | Conditional types allow us to infer types with the `infer` keyword. 749 | 750 | Info: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types 751 | 752 |
753 | 754 | ## Q20 755 | 756 | ```typescript 757 | interface Component { 758 | on_mount(): void; 759 | on_update(): void; 760 | on_destroy(): void; 761 | parent_ref?: Component; 762 | } 763 | 764 | type HookName = ParseHookNames; 765 | // ^ 766 | // "Mount" | "Update" | "Destroy" 767 | ``` 768 | 769 | Please declare the `ParseHookNames` type. 770 | 771 |
772 | Answer 773 | 774 | ```typescript 775 | type ParseHookName = 776 | T extends `on_${infer K}` 777 | ? Capitalize 778 | : never; 779 | 780 | type ParseHookNames = ParseHookName; 781 | ``` 782 | 783 | Info: https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#inference-with-template-literals 784 | 785 |
786 | 787 | ## Q21 788 | 789 | ```typescript 790 | abstract class Child { 791 | play() {}; 792 | } 793 | 794 | abstract class Adult { 795 | doWhatYouWant() {}; 796 | haveFun() {}; 797 | spendTimeWithLovedOnes() {}; 798 | rest() {}; 799 | } 800 | 801 | class Parent extends Adult { 802 | constructor(public children: [Child, ...Child[]]) { 803 | super(); 804 | } 805 | } 806 | 807 | class WorkingAdult extends Working(Adult) {} 808 | 809 | class WorkingParent extends Working(Parent) { 810 | haveSomeTimeOff() { 811 | this.children.forEach(child => child.play()); 812 | return super.haveSomeTimeOff(); 813 | } 814 | } 815 | 816 | function spendToday(workers: WorkingAdult[]): boolean { 817 | return workers.every(person => person.haveSomeTimeOff()); 818 | } 819 | ``` 820 | 821 | Please declare the `Working` function. 822 | 823 |
824 | Answer 825 | 826 | ```typescript 827 | type AbstractConstructor = abstract new (...args: any[]) => T; 828 | type AbstractClass = AbstractConstructor & { prototype: T }; 829 | 830 | interface WorkingImpl { 831 | work(): void; // just for presentational purposes 832 | haveSomeTimeOff(): boolean; 833 | } 834 | 835 | declare function Working>( 836 | Base: T 837 | ): AbstractClass & T; 838 | ``` 839 | 840 | Info: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-2.html#abstract-construct-signatures 841 | 842 |
843 | 844 | ## Q22 845 | 846 | ```typescript 847 | declare const uniqueSymbol: unique symbol; 848 | interface Foo { n: number; } 849 | interface Bar { n: number; } 850 | interface Baz { n: number; a: boolean; } 851 | 852 | type T01 = IsSame; // true 853 | type T02 = IsSame; // true 854 | type T03 = IsSame; // true 855 | type T04 = IsSame; // false 856 | type T05 = IsSame; // false 857 | type T06 = IsSame; // false 858 | type T07 = IsSame; // false 859 | type T08 = IsSame; // false 860 | type T09 = IsSame; // false 861 | type T10 = IsSame; // true 862 | type T11 = IsSame; // false 863 | type T12 = IsSame<1, number>; // false 864 | type T13 = IsSame; // true 865 | type T14 = IsSame; // false 866 | type T15 = IsSame; // true 867 | type T17 = IsSame; // false 868 | type T18 = IsSame<'X', string>; // false 869 | type T19 = IsSame>; // false 870 | type T20 = IsSame, Promise>; // true 871 | type T21 = IsSame; // true 872 | type T22 = IsSame; // false 873 | type T23 = IsSame; // true 874 | type T24 = IsSame<{}, object>; // false 875 | type T25 = IsSame<{}, {}>; // true 876 | type T26 = IsSame; // false 877 | type T27 = IsSame; // true 878 | type T28 = IsSame; // true 879 | type T29 = IsSame; // false 880 | type T30 = IsSame; // false 881 | ``` 882 | 883 | Please declare the `IsSame` type. We should be able to switch the two generic variable types and get the same result. 884 | 885 |
886 | Answer 887 | 888 | ```typescript 889 | type IsSame = 890 | (() => (T extends T1 ? 1 : 0)) extends 891 | (() => (T extends T2 ? 1 : 0)) 892 | ? true 893 | : false; 894 | ``` 895 | 896 | Credit: https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650 897 | 898 | How does it work? Here's a comment from the TypeScript codebase: 899 | 900 | > Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2, and Y1 is related to Y2. 901 | 902 | Let's rewrite our type to observe the resemblance: 903 | 904 | ```typescript 905 | type IsSame = 906 | (() => (T1 extends U1 ? 'X' : 'Y')) extends 907 | (() => (T2 extends U2 ? 'X' : 'Y')) 908 | ? true 909 | : false; 910 | ``` 911 | 912 | To resolve the outer conditional type, TypeScript checks if `U1` and `U2` are identical, and that's exactly what we need. 🤷‍♂️ 913 | 914 |
915 | 916 | ## Q23 917 | 918 | ```typescript 919 | interface Point { 920 | x: number; 921 | y: number; 922 | name?: string; 923 | } 924 | 925 | interface Circle { 926 | center: Point; 927 | radius: number; 928 | name?: string; 929 | } 930 | 931 | type RequiredKeysOfPoint = RequiredKeysOf; 932 | // "x" | "y" 933 | 934 | type RequiredKeysOfCircle = RequiredKeysOf; 935 | // "center" | "radius" 936 | ``` 937 | 938 | Please declare the "RequiredKeysOf" type and make sure it is reusable. 939 | 940 |
941 | Answer 942 | 943 | ```typescript 944 | type RequiredKeysOf = RequiredKeysOf1>; 945 | 946 | type RequiredKeysOf1 = { 947 | [K in keyof T]-?: {} extends Pick ? never : K; 948 | } [keyof T]; 949 | 950 | type RequiredKeysOf2 = { 951 | [K in keyof T]-?: T extends Record ? K : never; 952 | }[keyof T]; 953 | 954 | type RequiredKeysOf3 = keyof { 955 | [K in keyof T as {} extends Pick ? never : K]: unknown; 956 | }; 957 | 958 | type RequiredKeysOf4 = keyof { 959 | [K in keyof T as T extends Record ? K : never]: unknown; 960 | }; 961 | 962 | type OmitIndexSignatures = { 963 | [K in keyof T as {} extends Record ? never : K]: T[K]; 964 | } 965 | ``` 966 | 967 | All 4 options work as long as index signatures are removed. Options 1 & 2 reveal the keys even when all keys are required, whereas options 3 & 4 return `keyof T` (e.g. `keyof Point`). 968 | 969 |
970 | 971 | ## Q24 972 | 973 | ```typescript 974 | interface Bank { code: string; } 975 | interface Currency { code: string; sign: string; } 976 | 977 | type OpaqueBank = Opaque; 978 | type OpaqueCurrency = Opaque; 979 | type OpaqueIBAN = Opaque; 980 | 981 | interface BankAccount { 982 | bank: OpaqueBank; 983 | currency: OpaqueCurrency; 984 | holder: string; 985 | iban: OpaqueIBAN; 986 | } 987 | 988 | declare const account: BankAccount; 989 | 990 | // Assigning a value directly to "bank", "currency" or "iban" should be an error. 991 | account.bank = { code: "TCZBTR2AXXX" }; // Error 992 | account.bank = account.currency; // Error 993 | account.currency = { code: "USD", sign: "$" }; // Error 994 | account.iban = "TR320010009999901234567890"; // Error 995 | 996 | // But, it should be possible to use them as their base type. 997 | declare function formatCurrency(amount: number, currency: Currency): string; 998 | const formattedAmount = formatCurrency(128_000_000_000, account.currency); 999 | 1000 | declare function formatIBAN(iban: string): string; 1001 | const formattedIBAN = formatIBAN(account.iban); 1002 | 1003 | // This should be "bank" | "currency" | "iban". 1004 | type OpaqueKeysOfBankAccount = OpaqueKeysOf; 1005 | 1006 | // This should be "BANK" | "CURRENCY" | "IBAN". 1007 | type OpaqueTokensOfBankAccount = OpaqueTokensOf; 1008 | 1009 | // And, these should be allowed. 1010 | (account.bank as Transparent) = { code: "TCZBTR2AXXX" }; 1011 | (account.currency as Transparent) = { code: "USD", sign: "$" }; 1012 | (account.iban as Transparent) = "TR320010009999901234567890"; 1013 | (account as ClearOpaqueKeysOf).bank = { code: "TCZBTR2AXXX" }; 1014 | (account as ClearOpaqueKeysOf).bank = { code: "TCZBTR2AXXX" }; 1015 | (account as ClearOpaqueKeysOf).bank = { code: "TCZBTR2AXXX" }; 1016 | (account as ClearOpaqueKeysOf).bank = { code: "TCZBTR2AXXX" }; 1017 | (account as ClearOpaqueKeysOf).bank = { code: "TCZBTR2AXXX" }; 1018 | (account as ClearOpaqueKeysOf).bank = { code: "TCZBTR2AXXX" }; 1019 | (account as ClearOpaqueKeysOf).currency = { code: "USD", sign: "$" }; 1020 | (account as ClearOpaqueKeysOf).currency = { code: "USD", sign: "$" }; 1021 | (account as ClearOpaqueKeysOf).currency = { code: "USD", sign: "$" }; 1022 | (account as ClearOpaqueKeysOf).iban = "TR320010009999901234567890"; 1023 | (account as ClearOpaqueKeysOf).iban = "TR320010009999901234567890"; 1024 | (account as ClearOpaqueKeysOf).iban = "TR320010009999901234567890"; 1025 | (account as ClearOpaqueTokensOf).bank = { code: "TCZBTR2AXXX" }; 1026 | (account as ClearOpaqueTokensOf).bank = { code: "TCZBTR2AXXX" }; 1027 | (account as ClearOpaqueTokensOf).bank = { code: "TCZBTR2AXXX" }; 1028 | (account as ClearOpaqueTokensOf).bank = { code: "TCZBTR2AXXX" }; 1029 | (account as ClearOpaqueTokensOf).bank = { code: "TCZBTR2AXXX" }; 1030 | (account as ClearOpaqueTokensOf).bank = { code: "TCZBTR2AXXX" }; 1031 | (account as ClearOpaqueTokensOf).currency = { code: "USD", sign: "$" }; 1032 | (account as ClearOpaqueTokensOf).currency = { code: "USD", sign: "$" }; 1033 | (account as ClearOpaqueTokensOf).currency = { code: "USD", sign: "$" }; 1034 | (account as ClearOpaqueTokensOf).iban = "TR320010009999901234567890"; 1035 | (account as ClearOpaqueTokensOf).iban = "TR320010009999901234567890"; 1036 | (account as ClearOpaqueTokensOf).iban = "TR320010009999901234567890"; 1037 | 1038 | // But, these should not be allowed. 1039 | (account as ClearOpaqueKeysOf).bank = { code: "TCZBTR2AXXX" }; 1040 | (account as ClearOpaqueKeysOf).currency = { code: "USD", sign: "$" }; 1041 | (account as ClearOpaqueKeysOf).iban = "TR320010009999901234567890"; 1042 | (account as ClearOpaqueTokensOf).bank = { code: "TCZBTR2AXXX" }; 1043 | (account as ClearOpaqueTokensOf).currency = { code: "USD", sign: "$" }; 1044 | (account as ClearOpaqueTokensOf).iban = "TR320010009999901234567890"; 1045 | ``` 1046 | 1047 | Please define the `Opaque`, `Transparent`, `OpaqueKeysOf`, `OpaqueTokensOf`, `ClearOpaqueKeysOf`, and `ClearOpaqueTokensOf` types. 1048 | 1049 |
1050 | Answer 1051 | 1052 | ```typescript 1053 | declare const brand: unique symbol; 1054 | 1055 | type Branded = { 1056 | readonly [brand]: Token; 1057 | }; 1058 | 1059 | type Opaque = T & Branded; 1060 | 1061 | type Transparent> = 1062 | T extends Opaque 1063 | ? U 1064 | : T; 1065 | 1066 | type OpaqueKeysOf = { 1067 | [P in keyof T]: T[P] extends Branded ? P : never; 1068 | }[keyof T]; 1069 | 1070 | type OpaqueTokensOf = { 1071 | [P in keyof T]: T[P] extends Branded ? U : never; 1072 | }[keyof T]; 1073 | 1074 | type ClearOpaqueKeysOf> = { 1075 | [P in keyof T]: P extends K 1076 | ? T[P] extends Branded 1077 | ? Transparent 1078 | : T[P] 1079 | : T[P]; 1080 | }; 1081 | 1082 | type ClearOpaqueTokensOf> = { 1083 | [P in keyof T]: T[P] extends Branded 1084 | ? Token extends K 1085 | ? Transparent 1086 | : T[P] 1087 | : T[P]; 1088 | }; 1089 | ``` 1090 | 1091 | This is an approach to nominal typing in TypeScript, as described in this official example: https://www.typescriptlang.org/play#example/nominal-typing 1092 | 1093 |
1094 | 1095 | ## Q25 1096 | 1097 | ```typescript 1098 | interface App { 1099 | run(): void; 1100 | } 1101 | 1102 | type T1 = TupleOf; // [boolean, boolean, boolean, boolean]; 1103 | type T2 = TupleOf; // [number, number, number, number, number]; 1104 | type T3 = TupleOf; // [Date, Date, Date, Date, Date, Date]; 1105 | type T4 = TupleOf; // [string, ... 97 more ..., string]; 1106 | type T5 = TupleOf; // [App, ... 997 more ..., App]; 1107 | ``` 1108 | 1109 | Please declare the `TupleOf` type. You may hit a limit depending on your TypeScript version (use v4.5+) and implementation. 1110 | 1111 |
1112 | Answer 1113 | 1114 | ```typescript 1115 | type TupleOf< 1116 | T, 1117 | N extends number, 1118 | Acc extends T[] = [] 1119 | > = N extends Acc['length'] 1120 | ? Acc 1121 | : TupleOf; 1122 | ``` 1123 | 1124 | We don't have to worry about negative numbers, decimals, or `Infinity` as input because they will hit the limit and TypeScript will give an error. 1125 | 1126 |
1127 | 1128 | ## Q26 1129 | 1130 | ```typescript 1131 | type HelloWorld1 = Concat<["Hello", " ", "world"]>; 1132 | // "Hello world" 1133 | 1134 | type HelloWorld2 = Concat<["H", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"]>; 1135 | // "Hello world" 1136 | 1137 | type SingleLetter = Concat<["X"]>; 1138 | // "X" 1139 | 1140 | type EmptyString = Concat<[]>; 1141 | // "" 1142 | 1143 | type NonString = Concat<["a", "b", 3]>; 1144 | // ^ 1145 | // Error: Type 'number' is not assignable to type 'string'; 1146 | ``` 1147 | 1148 | Please declare the `Concat` type. 1149 | 1150 |
1151 | Answer 1152 | 1153 | ```typescript 1154 | type Concat = 1155 | T extends [ 1156 | infer Head extends string, 1157 | ...infer Tail extends string[] 1158 | ] 1159 | ? `${Head}${Concat}` 1160 | : ""; 1161 | ``` 1162 | 1163 | When the iteration is complete and `Head` becomes undefined, the outer condition fails because `undefined` doesn't extend `string`. 1164 | 1165 |
1166 | 1167 | ## Q27 1168 | 1169 | ```typescript 1170 | type T0 = Split<"">; 1171 | // [] 1172 | 1173 | type T1 = Split<"X">; 1174 | // ["X"] 1175 | 1176 | type T2 = Split<"Hello world">; 1177 | // ["H", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"] 1178 | 1179 | type T3 = Split<"CR#7">; 1180 | // ["C", "R", "#", "7"] 1181 | ``` 1182 | 1183 | Please declare the `Split` type. 1184 | 1185 |
1186 | Answer 1187 | 1188 | ```typescript 1189 | type Split = 1190 | T extends `${infer Head}${infer Tail}` 1191 | ? [Head, ...Split] 1192 | : []; 1193 | ``` 1194 | 1195 | Adding a delimiter parameter to both `Split` and `Concat` (from [#Q26](#q26)) is possible: 1196 | 1197 | ```typescript 1198 | type Split = 1199 | "" extends T 1200 | ? [] 1201 | : T extends `${infer Head}${Delimiter}${infer Tail}` 1202 | ? [Head, ...Split] 1203 | : [T]; 1204 | 1205 | type Concat = 1206 | T extends [infer Head extends string, ...infer Tail extends string[]] 1207 | ? `${Head}${Delimiter}${Concat}` 1208 | : ""; 1209 | 1210 | 1211 | type S0 = Split<"">; // [] 1212 | type C0 = Concat; // "" 1213 | 1214 | type S1 = Split<"", "X">; // [] 1215 | type C1 = Concat; // "" 1216 | 1217 | type S2 = Split<"X">; // ["X"] 1218 | type C2 = Concat; // "X" 1219 | 1220 | type S3 = Split<"X", "X">; // [""] 1221 | type C3 = Concat; // "X" 1222 | 1223 | type S4 = Split<"Hello world", " ">; // ["Hello", "world"] 1224 | type C4 = Concat; // "Hello world" 1225 | 1226 | type S5 = Split<"CR#7">; // ["C", "R", "#", "7"] 1227 | type C5 = Concat; // "CR#7" 1228 | 1229 | type S6 = Split<"CR#7", "#">; // ["CR", "7"] 1230 | type C6 = Concat; // "CR#7" 1231 | ``` 1232 | 1233 |
1234 | 1235 | ## Q28 1236 | 1237 | ```typescript 1238 | type T0 = Reverse<[]>; // [] 1239 | type T1 = Reverse<[boolean]>; // [boolean] 1240 | type T2 = Reverse<[null, void]>; // [void, null] 1241 | type T3 = Reverse<["x", "y", "z"]>; // ["z", "y", "x"] 1242 | type T4 = Reverse<[0, 1, 2, 3, 4]>; // [4, 3, 2, 1, 0] 1243 | type T5 = Reverse<[{}, object]>; // [object, {}] 1244 | ``` 1245 | 1246 | Please declare the `Reverse` type. 1247 | 1248 |
1249 | Answer 1250 | 1251 | ```typescript 1252 | type Reverse = 1253 | T extends [infer Head, ...infer Tail] 1254 | ? [...Reverse, Head] 1255 | : []; 1256 | ``` 1257 | 1258 | Bonus (string variant): 1259 | 1260 | ```typescript 1261 | type ReverseStr = 1262 | T extends `${ 1263 | infer Head extends string 1264 | }${ 1265 | infer Tail extends string 1266 | }` 1267 | ? `${ReverseStr}${Head}` 1268 | : ''; 1269 | 1270 | type T0 = ReverseStr<''>; // "" 1271 | type T1 = ReverseStr<'X'>; // "X" 1272 | type T2 = ReverseStr<'hello'>; // "olleh" 1273 | ``` 1274 | 1275 |
1276 | 1277 | ## Q29 1278 | 1279 | ```typescript 1280 | class Bar extends Foo {} 1281 | class Baz extends Foo { 1282 | constructor(public readonly x: number, public readonly y: number) { 1283 | super(); 1284 | } 1285 | } 1286 | class Qux extends Baz {} 1287 | 1288 | const foo = Foo.create(); 1289 | // ^ 1290 | // Error: Cannot assign an abstract constructor type to a non-abstract constructor type. 1291 | 1292 | const bar = Bar.create(); 1293 | // OK. Type of bar is Bar. 1294 | 1295 | const baz = Baz.create(); 1296 | // ^ 1297 | // Error: Expected 2 arguments, but got 0. 1298 | 1299 | const qux = Qux.create(0, 1); 1300 | // OK. Type of qux is Qux. 1301 | ``` 1302 | 1303 | Please declare the abstract `Foo` class and implement the static `create` method. It doesn't have to do anything other than returning an instance. 1304 | 1305 |
1306 | Answer 1307 | 1308 | ```typescript 1309 | abstract class Foo { 1310 | static create< 1311 | T extends Foo, 1312 | Args extends unknown[] 1313 | >(this: new (...args: Args) => T, ...args: Args) { 1314 | return new this(...args); 1315 | } 1316 | } 1317 | ``` 1318 | 1319 | Info: https://www.typescriptlang.org/docs/handbook/2/classes.html#static-members 1320 | 1321 |
1322 | 1323 | ## Q30 1324 | 1325 | ```typescript 1326 | type T01 = IsGTE<0, 0>; // true 1327 | type T02 = IsGTE<0, 1>; // false 1328 | type T03 = IsGTE<1, 0>; // true 1329 | type T04 = IsGTE<1, 1>; // true 1330 | type T05 = IsGTE<42, 24>; // true 1331 | type T06 = IsGTE<42, 99>; // false 1332 | type T07 = IsGTE<999, 1000>; // false 1333 | type T08 = IsGTE<1000, 999>; // true 1334 | type T09 = IsGTE<1000, 1000>; // true 1335 | type T10 = IsGTE<-0, -0>; // true 1336 | type T11 = IsGTE<-0, -1>; // true 1337 | type T12 = IsGTE<-1, -0>; // false 1338 | type T13 = IsGTE<-1, -1>; // true 1339 | type T14 = IsGTE<-42, -24>; // false 1340 | type T15 = IsGTE<-42, -99>; // true 1341 | type T16 = IsGTE<-999, -1000>; // true 1342 | type T17 = IsGTE<-1000, -999>; // false 1343 | type T18 = IsGTE<-1000, -1000>; // true 1344 | type T19 = IsGTE<0, -0>; // true 1345 | type T20 = IsGTE<0, -1>; // true 1346 | type T21 = IsGTE<1, -1>; // true 1347 | type T22 = IsGTE<42, -24>; // true 1348 | type T23 = IsGTE<42, -42>; // true 1349 | type T24 = IsGTE<1000, -1000>; // true 1350 | type T25 = IsGTE<-0, 0>; // true 1351 | type T26 = IsGTE<-1, 0>; // false 1352 | type T27 = IsGTE<-1, 1>; // false 1353 | type T28 = IsGTE<-42, 0>; // false 1354 | type T29 = IsGTE<-42, 42>; // false 1355 | type T30 = IsGTE<-1000, 1000>; // false 1356 | ``` 1357 | 1358 | Please declare the `IsGTE` type. Generic parameters will always be numeric literals. 1359 | 1360 |
1361 | Answer 1362 | 1363 | ```typescript 1364 | type And boolean)[], I extends 0[] = []> = 1365 | I['length'] extends T['length'] 1366 | ? true 1367 | : ReturnType extends false 1368 | ? false 1369 | : And; 1370 | 1371 | type Or boolean)[], I extends 0[] = []> = 1372 | I['length'] extends T['length'] 1373 | ? false 1374 | : ReturnType extends true 1375 | ? true 1376 | : Or; 1377 | 1378 | type Not = T extends true ? false : true; 1379 | 1380 | type IsEqual = 1381 | (() => (T extends N1 ? 1 : 0)) extends 1382 | (() => (T extends N2 ? 1 : 0)) 1383 | ? true 1384 | : false; 1385 | 1386 | type IsNegative = `${T}` extends `-${number}` ? true : false; 1387 | 1388 | type IsSmallerNegative< 1389 | A extends string, 1390 | B extends string, 1391 | I extends 0[] = [] 1392 | > 1393 | = `-${I['length']}` extends infer M extends A | B 1394 | ? M extends A ? false : true 1395 | : IsSmallerNegative; 1396 | 1397 | type IsGTE< 1398 | A extends number, 1399 | B extends number 1400 | > = 1401 | Or<[ 1402 | () => IsEqual, 1403 | 1404 | () => And<[ 1405 | () => IsNegative, 1406 | () => Not> 1407 | ]>, 1408 | 1409 | () => And<[ 1410 | () => IsNegative, 1411 | () => IsNegative, 1412 | () => IsSmallerNegative<`${B}`, `${A}`> 1413 | ]>, 1414 | 1415 | () => And<[ 1416 | () => Not>, 1417 | () => Not>, 1418 | () => IsSmallerNegative<`-${A}`, `-${B}`> 1419 | ]>, 1420 | ]>; 1421 | ``` 1422 | 1423 |
1424 | --------------------------------------------------------------------------------