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

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions