Typescript: Design Meeting Notes, 1/31/2020

Created on 5 Feb 2020  ·  6Comments  ·  Source: microsoft/TypeScript

Should an Optional Chain Always Add undefined to the Type?

https://github.com/microsoft/TypeScript/issues/36163#issuecomment-574359197

const arr = [1, 2, 3, 4, 5]
const j = Math.random() * 1000 % 1;

const k = arr[j]?.toFixed();
  • If index signatures included undefined, you'd get undefined.

    • But this can go beyond that case.

  • Defensive code in general.
  • But we have plenty of places where we don't make || conservative.

    // Inferred to `number[]` because we
    // don't assume it *could* be null/undefined
    function foo(x: number[]) {
        return x || "hello"
    }
    
  • Okay, but are you going to trust that the user wrote ?. for a reason, or assume we have a perfect type system?
  • Seems like the indexing case is hard to explain to users

    • Okay, but maybe we could fix the indexing case?



      • Really a separate issue



  • Are people encountering this?

    • It's a new feature, and the longer we wait, the more we may hear about this.

  • Why would you write ?. on a defined type unless you planned to immediately handle them?

    • The indexing case?

    • Then let's solve the indexing case.

  • Should we also change || and &&?

    • Maybe!

    • What!? This behavior is so old, who exactly are you helping by changing that?

    • Pretty sure we need code like this to work because of how narrowing within arrays works today.

  • Discussion inconclusive

Augmenting Keys

12754

  • Some demand to do two things:

    • Slicing/dicing strings to convert from/to camelCase, kebab-case, etc.

    • Filtering out types

    • And maybe just concatenating literals

  • Would be nice to write

    type Foo<T> = {
      [K in keyof T as StringConcat<"get-", K>]: T[K]
    };
    
    • Could you do something like the following?

      type Foo<T> = {
        [K in StringConcat<"get-", keyof T>]: T[K]
      };
      
  • Created a prototype that wires up a bunch of type aliases that have special behavior:

    type StringToUpper<K> = ...; // StringToUpper<"Foo"> -> "FOO"
    type StringToLower<K> = ...; // StringToLower<"Foo"> -> "foo"
    
    // StringSlice<"hello", 1> -> "ello"
    type StringSlice<K, Start extends number, End extends number> = ...;
    
    // StringConcat<"foo", "bar"> -> "foobar"
    type StringConcat<K1, K2> = ...;
    
    // StringMatch<"foo" | "bar" | "baz", "a"> -> "bar" | "baz"
    type StringMatch<K, Pattern extends string> = ...;
    
  • Won't these cause a lot of exponential explosions in the type system?

    • Sure, but we already have those.

      • Yes, But we're already trying to grapple with them.

  • Can we not use type aliases for everything here?

    • Not clear that it's better.

    • Strange that there's a magical name that has behavior instead of something core to the language.



      • But creating more operators/keywords would be a lot of new work in the parser.



  • Many of these aren't useful on generics - what if we just let people write actual code to do this?

    • Now you have to have a API on our type system.

  • Do we think this is something we want to solve?

    • Need to see the use-cases - what are people doing today?



      • Code generators? Index signatures?



    • Can't necessarily say "yes", but probably not near-term. We should be focusing on speed.

Design Notes

Most helpful comment

Hey @DanielRosenwasser ,

are there plans to implement the string utility types? Are you seeking contributors for those? I'd like to help, as I'm very much interested in this feature. Although I have no experience working with the TS code base at all, I'd like to invest my time. Maybe it could help me fix one bug or another in the future 😄

All 6 comments

Why would you write ?. on a defined type unless you planned to immediately handle them?

People new to TS often carry the need to write defensive code _everywhere_ even if it's unnecessary. If TS implicitly added | undefined here, it would suddenly make these legitimate and make it necessary to add even _more_ defensive code to handle the new undefined values that aren't really there at runtime.

This also seems like it would break the no-unnecessary-condition lint rule, which is really useful for changing this behavior over time.

Misc. thoughts from a random user:

  • For the string concatenation case, is there a reason we couldn't just use the + operator?

    • This would mean we'd have syntactic parity between value-level concatenation and type-level concatenation

  • There's already precedent in Flow for built-in "type aliases" (e.g. $Call, $Exact, etc), so it wouldn't be too crazy IMO if TypeScript were to introduce a few

    • In fact, for better or worse, doing so would allow you guys to special-case more complex typing solutions without adding new syntax (e.g. $Awaited, $Coroutine, etc)

We don't like the type aliases (especially the leading $), but yeah, it's the easiest way to make this "scale".

Hey @DanielRosenwasser ,

are there plans to implement the string utility types? Are you seeking contributors for those? I'd like to help, as I'm very much interested in this feature. Although I have no experience working with the TS code base at all, I'd like to invest my time. Maybe it could help me fix one bug or another in the future 😄

No plans right now - we had a mock-up demonstrated at the design meeting, but the concern was more about complexity and speed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fwanicka picture fwanicka  ·  3Comments

MartynasZilinskas picture MartynasZilinskas  ·  3Comments

dlaberge picture dlaberge  ·  3Comments

bgrieder picture bgrieder  ·  3Comments

uber5001 picture uber5001  ·  3Comments