Skip to content

Long tuples lose appropriate type information #11924

Closed

Description

@jocull

TypeScript Version:
2.0.3 / nightly (2.1.0-dev.201xxxxx)

Limit is seemingly even lower in 1.8.9, with type information loss after the tuple is 6 or more elements wide.

Code

// A *self-contained* demonstration of the problem follows...
Promise.all([
    Promise.resolve(1),
    Promise.resolve(''),
    Promise.resolve(new Date()),
]).then(results => {
    let [
        a, // Number
        b, // String
        c, // Date - Everything is ok!
    ] = results;
});

Promise.all([
    Promise.resolve(1),
    Promise.resolve(''),
    Promise.resolve(new Date()),
    Promise.resolve({}),
    Promise.resolve({ hi: 'hello' }),
    Promise.resolve([]),
    Promise.resolve(null as string),
    Promise.resolve(1),
    Promise.resolve(''),
    Promise.resolve(new Date()),
    Promise.resolve({}), // Length == 11
]).then(results => {
    let [
        a, // Everything is {} now?
        b,
        c,
        d,
        e,
        f,
        g, 
        h,
        i,
        j,
        k, // << -- Error!
           // "Tuple with length 10 cannot be assigned to tuple with length 11"?
    ] = results;
});

Code on the playground.

Expected behavior:
Tuple should be destructured with the appropriate types.

Actual behavior:
Types are lost after a seemingly arbitrary tuple limit. Actual tuple length is not understood by the compiler.

Workaround:
Break work up into smaller tuples below the limit and assemble them after. This will preserve type information.

Activity

  1. blakeembrey commented on Oct 28, 2016

    @blakeembrey
    Contributor

    This comes from the promise definition, the maximum overload of tuples in the definition is 10. See https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es6.d.ts#L5289.

  2. jocull commented on Oct 28, 2016

    @jocull
    Author

    Tuple types are apparently not inferred anywhere else. Why is that since the types are definitely known in cases such as this?

    let x = [1, 'two'];
    let [
        a, // string | number
        b, // string | number
    ] = x;

    Code on the playground.

  3. RyanCavanaugh commented on Oct 28, 2016

    @RyanCavanaugh
    Member

    Tuple types are apparently not inferred anywhere else. Why is that since the types are definitely known in cases such as this?

    Because we can't meaningfully distinguish these two cases:

    let x = [1, 'two'];
    console.log(x.indexOf(1)); // no side effects
    let [
        a, // string | number
        b, // string | number
    ] = x;
    let x = [1, 'two'];
    x.sort(); // now what?
    let [
        a, // string | number
        b, // string | number
    ] = x;
  4. RyanCavanaugh commented on Oct 28, 2016

    @RyanCavanaugh
    Member

    It's also not at all apparent that the fact that you wrote

    let x = [1, "two"];

    implies that you want it to be illegal to write

    x[0] = "one";
  5. jocull commented on Nov 2, 2016

    @jocull
    Author

    You guys can close this issue if you want - no need to leave it cluttering more relevant issues.

    I appreciate you taking the time to answer my questions. Everyone has clearly thought through the issues and edge cases here!

  6. pelotom commented on Dec 23, 2016

    @pelotom

    Ryan Cavanaugh (@RyanCavanaugh) the fact that you can't meaningfully distinguish between them is a problem no matter what though:

    // This type checks even though it shouldn't
    function bad(foo: [number, string]): [number, string] {
      return foo.sort()
    }

    It seems that while representing tuples as arrays at runtime is fine and dandy, perhaps at compile time they should be kept distinguished, i.e. [number, string] and (number|string)[] should be considered mutually incompatible types.

  7. aluanhaddad commented on Dec 24, 2016

    @aluanhaddad
    Contributor

    [number, string] and (number|string)[] should be considered mutually incompatible types.

    The example actually typechecks because the signature of sort is

    sort(compareFn?: (a: T, b: T) => number): this;

    Unlike the example code from #13151, foo in

    function bad(foo: [number, string]): [number, string] {
      return foo.sort()
    }

    has a tuple type.

  8. pelotom commented on Dec 24, 2016

    @pelotom

    I know, I'm saying that tuples should not be considered subtypes of Array, in which case the sort method wouldn't be available on them. It makes no sense to sort a tuple.

  9. danvk commented on Oct 4, 2017

    @danvk
    Contributor

    FYI, you can work around this issue for Promises by using an object, rather than an array.

    async function promiseObject<T>(obj: {[k in keyof T]: Promise<T[k]>}): Promise<T> {
      const keys = Object.keys(obj);
      const promises = keys.map(k => (obj as any)[k]);
      const values = await Promise.all(promises);
      const out = {} as any;
      keys.forEach((k, i) => {
        out[k] = values[i];
      });
      return out;
    }

    here's usage:

    describe('promiseObject', () => {
      it('should await an object of promises', async () => {
        const {a, b} = await promiseObject({
          a: Promise.resolve(1),
          b: Promise.resolve('b')
        });
        expect(a).to.equal(1);
        expect(b).to.equal('b');
      });
    });
  10. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ExternalRelates to another program, environment, or user action which we cannot control.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions