Skip to content

Design Meeting Notes, 8/24/2018 #26952

Closed
Closed
@DanielRosenwasser

Description

@DanielRosenwasser

Call-site inference for implicit anys

This is a demo presentation

  • [[Opens up minimatch]]
  • "This is the codebase you open up to make yourself feel good about JavaScript"
  • Looking at the charSet declaration function
    • We have noImplicitAny on
    • See that it was at some point called with a string, and so we change the parameter type.
    • If we observe a circularity, we fall back to any and report an error.
  • Is this TypeScript too?
    • It works for either JavaScript or TypeScript
  • How bad is it?
    • The graphs are confusing, but it's over 4.5x slower to look at how the function itself is called to figure out the parameter types.
    • If you only look at use-sites of parameters within the bodies of functions, it'x ~1.8x slower.
  • We mostly imagine that this would be a one-time shot tool
  • Could also imagine that we'd improve the performance here, but it's decent
  • Did we look at the tests at all? You'd figure unit tests would be great for figuring out use-site.
    • But that's a problem for stupid tests like "assert that this throws when I pass in a number"
  • But you get 40% more type coverage on Webpack.
    • That's like half the way there.
    • But even that's not worth slowing down the type-checker so dramatically.
    • Seems like we should be leveraging this somehow.

Weird behavior for spreading arrayish types

#26110

  • We now allow rest parameters to have a generic type (T).

    • The requirement has been that that generic type needs to extend an array (e.g. T extends any[]).
  • But you can come up with several things that actually satisfy arrays.

    • Object subtypes:

      interface CoolArray extends Array<string>
      function foo<T extends any[]>(...args: T): T {
          throw 0;
      }
      foo<CoolArray>();
  • Problem: what does it mean to spread in these object subtypes?

    • For these object subtypes, any argument list translated to an array or a tuple won't ever satisfy these types.
  • Solution: if you ever have a type parameter constrained to something other than an Array or ReadonlyArray, and you are trying to relate to a generic rest parameter, you infer a tuple type from the matching argument and try to infer from that.

    • If that inference fails, TypeScript falls back to the constraint and relates against that.
  • Union subtypes:

    function foo<T extends [] | [number, number]>  (...args: T): T {
        throw 0;
    }
    foo<CoolArray>();

Named type arguments, associated types, and partial inference

  • In Named Type Arguments & Partial Type Argument Inference #23696, we realized that making every type parameter nameable would be problematic for existing APIs - it exposes what were thought of as mostly implementation details.

  • What problems were we trying to solve?

    1. Positional type arguments are problematic because it locks you down.
    2. Using a type parameter as a default for a temporary/local type.
      • StrictEventEmitter
      • "Is this a real type!?"
    3. Specialized subclasses:
    ```ts
    class Box<T> { x: T }
    class SpecialBox extends Box<Speical>
    
    // How do I specialize this further without making SpecialBox generic?
    class SpecialSpecialBox extends SpecialBox {
    }
    ```
    
  • At a high level, we want to be able to place types within types.

    declare class BoxObserver<T> {
        type Boxified = { value: T };
        get(): Boxified;
    }
    let x: BoxObserver<number, 
    • Hold up though, that means in this class you can't write the following, since you don't know if Boxified will be further specialized.

      let x: T = this.get().value
  • Feels strange that these can appear in the type parameter list.

  • One of the core differences is that we also don't do inference on these types.

  • Also, these must be associated with the instance.

    • They must be, because they close over the instance type parameters.
  • If changing the type in the subtype changes the behavior in the supertype, then it must be a type variable.

  • But there are merits to just having this as an alias.

    • Could imagine

      declare class Foo<T> with Boxified = { value: T } {
          get(): Boxified;
      }
  • Also, feels strange that there is some mixing of type parameters and aliases:

    type Foo<T> =
        type X = Foo<T>[] in {
            // ...
        };

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design NotesNotes from our design meetings

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions