Skip to content

Issues around casting tuples to arraysΒ #62177

@Andarist

Description

@Andarist

πŸ”Ž Search Terms

comparable relation casting comparison equality array tuple properties index infos

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=6.0.0-dev.20250802#code/JYOwLgpgTgZghgYwgAgPICMBWEFgDLCRRwA2yA3gFDLIDaA1hAJ4BcyAzmFKAOYC6bOCCYBuSgF9KlACY4ScKCgTz27ZAAUA9pwrVkwaWxABXALbpoYmmEIkIbTtxA8xkmXIVLNIHaAAOxmAAjGwAFBjYuAREpMgAPsgmJCQAlLR8YgjeOghwnBDSQcgAvPogAcHIeRraYOkiyAD0jcia9FKyyp7IWT5gZRUATGy0ETj4hNCxCUkkADRoWOPRU2QzxskZlL05eZDSgyUDgYfVWpz1TS3QUJpQ7AvogchgABYoN3f6aiDZED7AXJkHjAABu-xe72Qpn+Nm8BReTD8EDU3XBUCY0OMCFePU0pj8Cjg6DsyAAtFU1OxXpoAO4gZAWEh0joeRR4vrHMAAZjYYyik2Ia0SGxImWy-Vy+Wk3KO-kCsrOtQazVa7SAA

πŸ’» Code

interface ObjectLiteral {
  [key: string]: any;
}

declare class Post {
  id: number;
  title: string;
}

declare const input1: (ObjectLiteral | null)[];
const casted1 = input1 as Post[]; // ok

declare const input2: [ObjectLiteral | null, ObjectLiteral | null];
const casted2 = input2 as Post[]; // errors, but the error is nonsensical given the mentioned types are very much comparable - as shown below

declare const input3: ObjectLiteral | null;
const casted3 = input3 as Post; // ok

πŸ™ Actual behavior

the second cast fails and the user is left with a very confusing error given the third cast suceeds

πŸ™‚ Expected behavior

I'd expect the second cast to succeed

Additional information about the issue

In the compiler's code we can find this definition:

A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T.

Under this definition, this cast should be allowed:

const value = [
  { id: 1, title: "foo" },
  { id: 2, title: "bar" },
] as const satisfies any[];

const input2: [ObjectLiteral | null, ObjectLiteral | null] = value; // ok
const castTarget2: Post[] = value; // ok

More than that, some other related cases don't seem to adhere to this definition:

type PoorMansTuple = {
  [K: number]: { x: number };
  0: { x: number };
};

type PoorMansArray = {
  [K: number]: { x: number; y: number };
};

declare const tupleLike: PoorMansTuple;
declare const arrayLike: PoorMansArray;

// Conversion of type 'PoorMansTuple' to type 'PoorMansArray' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
//   'number' index signatures are incompatible.
//     Property 'y' is missing in type '{ x: number; }' but required in type '{ x: number; y: number; }'.(2352)
tupleLike as PoorMansArray;
// Conversion of type 'PoorMansArray' to type 'PoorMansTuple' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
//   Property '0' is missing in type 'PoorMansArray' but required in type 'PoorMansTuple'.(2352)
arrayLike as PoorMansTuple;

In those cases, the reported errors are also confusing. At the very least, a cast between types mentioned by the error on tupleLike as PoorMansArray is allowed. A missing property like it doesn't make a cast invalid.

This also extends to comparisons, given they also use comparable relation:

const x: [{ x: number; y: number }] = [{ x: 123, y: 456 }];

const tuple: [{ x: number }] = x; // ok
const array: { x: number; y: number }[] = x; // ok

// This comparison appears to be unintentional because the types '[{ x: number; }]' and '{ x: number; y: number; }[]' have no overlap.(2367)
if (tuple === array) {}

Thanks to @LukeAbby for discussing this stuff with me.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions