Skip to content

String index into a generic type having a string index signature isn't assignable to indexed access type #51127

Open
@jcalz

Description

@jcalz

Bug Report

🔎 Search Terms

string index signature; indexed access; generic; 2322; not assignable

🕗 Version & Regression Information

This seems to be there all the way back to TS3.5, maybe introduced by #30769, although I'm not sure I understand why.

-This changed between versions 3.3.3 and 3.5.1

⏯ Playground Link

Playground link with relevant code

💻 Code

interface Foo {
    a: { [k: string]: string | undefined };
    b: { [k: string]: number | undefined };
    c: { [k: string]: boolean | undefined };
}
function get<K extends keyof Foo>(foo: Foo, k: K, x: string): Foo[K][string] {
    return foo[k][x]; // error!
    // Type 'string | number | boolean | undefined' is not assignable to type 'Foo[K][string]'.
}

🙁 Actual behavior

Reading a property with a string key from a Foo[K] object does not produce a value of type Foo[K][string], even in a non-widening position.

🙂 Expected behavior

Indexing into a Foo[K] value with a string key should produce a property of type Foo[K][string] if such a type is in a non-widening position.


This might be similar to #31661 and #33181 but I can't tell.

Note that numeric index signatures work as expected:

interface Foo {
    a: { [k: number]: string | undefined };
    b: { [k: number]: number | undefined };
    c: { [k: number]: boolean | undefined };
}
function get<K extends keyof Foo>(foo: Foo, k: K, x: number): Foo[K][number] {
    return foo[k][x]; // okay    
}

And known literal keys work as expected:

interface Foo {
    a: { x: string | undefined };
    b: { x: number | undefined };
    c: { x: boolean | undefined };
}
function get<K extends keyof Foo>(foo: Foo, k: K, x: "x"): Foo[K]["x"] {
    return foo[k][x]; // okay    
}

But string index signatures (and symbol also) fail for some reason.

This can be worked around easily enough:

function get<K extends keyof Foo>(foo: Foo, k: K, x: keyof Foo[K]): Foo[K][keyof Foo[K]] {
    return foo[k][x]; // okay
}

But I'm wondering what's going on.

Playground link with above examples

(note to self: go back to this SO answer if I acquire more cluefulness here)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.SuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions