Skip to content

Design Meeting Notes, 3/11/2022 #48226

Closed
Closed
@DanielRosenwasser

Description

@DanielRosenwasser

Variance and Variance Annotations on Type Parameters

#48080 (comment)

#10717

(continued from #48209)

Playground Link

type Foo<T> = {
    x: T;
    f: FooFn<T>;
};

type FooFn<T> = (foo: Bar<T[]>) => void;

type Bar<T> = {
    value: Foo<T[]>;
};

// Try this in TypeScript 4.6.
// Whether or not you see an error on 'fn2 = fn1'
// depends on whether or not Section (B) comes before Section (A)

// Section (A)

declare let foo1: Foo<string>;
declare let foo2: Foo<unknown>;
foo1 = foo2;
foo2 = foo1;

// Section (B)

declare let fn1: FooFn<string>;
declare let fn2: FooFn<unknown>;

fn1 = fn2;
fn2 = fn1;
  • Could handle these better, but would break code.

  • Idea: variance annotations

    • Compare T by covariant rules:

      type Foo<out T> = {
          x: T;
          f: FooFn<T>;
      };
    • Compare T by contravariant rules:

      type Foo<in T> = {
          x: T;
          f: FooFn<T>;
      };
    • Compare T by invariant rules:

      type Foo<in out T> = {
          x: T;
          f: FooFn<T>;
      };
  • Can help with documentation, correctness, perf.

    • Perf results? 10-20% improvements because we have to measure variance.
    • This short-circuits any circularity.
  • Existing code that doesn't use these gets nothing out of it.

    • Should we emit these annotations in .d.ts files if we can accurately measure?
      • We can come up with states like bivariant, unmeasurable, and independent. Won't try to represent those. But for some subset (i.e. covariant, contravariant, invariant), we could do that.
      • But introducing these in declaration files would be a breaking change for downstream .d.ts consumers!
      • Start out by not doing this unless a user explicitly specified it.
  • Would be nice if we could surface this information in quick info though.

  • Interface merging combines the variance annotations.

    • Does that mean that interface emerging can invalidate the annotation of another interface?
      • Yes, but this was always the case. The compiler will issue an error and you'll manually have to fix that up.
  • Variance annotations on generic signatures we don't allow - doesn't make sense.

    • Doesn't it? We do a lot of work on this sort of thing.
  • Error messages?

    • Could special-case the last part.
    • Avoid saying "covariant" or "contravariant" - "input position" and "output position".
    • We report based on the marker types, using Super and Sub as the displayed names.
      • Those could be real names.
  • Who would benefit the most from the perf aspect here?

    • xstate, lodash, immutable, Ember?
    • Could use typesVersions to just give an instant perf boost to 4.7 users if this ships by then.
    • We don't have a way to measure "how much time is being spent in variance"
  • Maybe urge caution.

    • Lots of stuff that stops working if you start marking everything as in out (invariant).
  • Do we feel okay about the fact that two instantiations of the same type might be incompatible but structurally identical types would be compatible?

    • Yes-ish.
      • Usually the names of types are not material.
    • Really means we have to message this well.
    • Invariant is the only place where you can throw off how the type-checker is usually used.
      • Well if you say co/contravariant when we would've otherwise measured bivariant.
    • in out future-proofs the API - kinda nice.
  • So declaration emit?

    • No, not automatically. Breaking change for older versions.
  • This will force us to create a 4.7-based rift on DefinitelyTyped.

    • typesVersions?
    • If you use JSDoc, it's ignored by older versions.
    • Out of band file?
  • Should we have some sort of support for JSDoc here?

  • How does this play with "Types as Comments"?

    • Doesn't affect it, the proposal ignores content between < and >
  • JSDoc for variance annotations?

    • It might be the case that we need to lean on JSDoc for public, private, protected.
    • So in and out.
  • Our node factory APIs would need to take a breaking change.

    • We've done this, but people understandably are unhappy about it.
    • Optional parameters? Arity check won't work.
    • NodeFlags?
    • Overload that checks the first argument, and maybe deprecates the old overload?
  • Back to syntax!

    • So actual keywords?
      • Minimally yes, but also indecisive about back-compat and allowing /** @in @out */

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