Skip to content

Implementation of concat gives unexpected results #39241

Closed

Description

function concat1<T extends readonly any[], U extends readonly any[]>(arr1: T, arr2: U): [...T, ...U] {
    return [...arr1, ...arr2];
}

function concat2<T extends readonly any[], U extends readonly any[]>(arr1: T, arr2: U) {
    return [...arr1, ...arr2];
}

const x1 = concat1([1,2,3] as const, [4,5, 6] as const);
const x2 = concat2([1,2,3] as const, [4,5, 6] as const);

Here, TypeScript is okay with concat1 returning [...T, ...U], but instead infers the type of concat2 to be T[number][].

Notice U was not in the output type!

As a result, x1 has the type [1, 2, 3, 4, 5, 6] and x2 has the type (1, 2, 3)[]).

Activity

  1. DanielRosenwasser commented on Jun 24, 2020

    @DanielRosenwasser
    MemberAuthor

    Feels strange to contrast with tail which works as one would expect:

    // returns 'T'
    function tail<T extends any[]>(arg: [any, ...T]) {
        const [_, ...result] = arg;
        return result
    }
  2. DanielRosenwasser commented on Jun 24, 2020

    @DanielRosenwasser
    MemberAuthor

    Anders Hejlsberg (@ahejlsberg) any thoughts on this one?

  3. ahejlsberg commented on Jun 25, 2020

    @ahejlsberg
    Member

    Yeah, inferring T[number][] instead of (T[number] | U[number])[] definitely isn't good. We actually do ask for a union of those two types, but sadly subtype reduction reduces it to T[number][]. The issue is the rule we have that says a type is assignable to T[K] if it is assignable to the base constraint of T[K] for writing. And the base constraint of T[K] for both reading and writing is any. The rule is unsound (for sure), but we have it to allow assignment of non-generic types to type this['foo'] in classes. I'm thinking we should restrict it to not kick in for index signatures in T.

  4. ahejlsberg commented on Jun 25, 2020

    @ahejlsberg
    Member

    BTW, the fact that we infer an array type for concat2 is by design, it doesn't have a tuple contextual type nor is it as const. Not clear that we could easily change that.

  5. ahejlsberg commented on Jun 25, 2020

    @ahejlsberg
    Member

    Actually, there's really no reason the unsound rule should be in effect for anything but the assignable and comparable relations. So, I think the fix is to disable it for the subtype relations, which causes us to preserve the full (T[number] | U[number])[]. I will put up a PR.

  6. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScript

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions