Skip to content

Generic Function with Indexed Type ignores readonly #18374

Closed
@Nathan-Fenner

Description

@Nathan-Fenner

TypeScript Version: 2.4.2

Code

function mapAssign<T, K extends keyof T>(obj: T, k: K, f: (v: T[K]) => T[K]) {
    obj[k] = f(obj[k]);
}

type NoChange = {
    readonly value: number
}
const obj: NoChange = {value: 5};

mapAssign(obj, "value", x => x+1);

Expected behavior:
Since NoChange.value is readonly, it should not be modifiable.

Actual behavior:
The code compiles and runs, modifying obj.value.

This might just be a design limitation that can't be worked around.

If a stricter check was desirable, I could imagine it being something like this:

  • if K extends keyof T and k: K and x: T, then x[k] = e would fail, but T = {readonly [k]: any} would be okay
  • if K extends !readonly keyof T and k: K and x: T then x[k] = e would succeed, but T = {readonly [k]: any} would fail

So the above program would become

function mapAssign<T, K extends !readonly keyof T>(obj: T, k: K, f: (v: T[K]) => T[K]) {
    obj[k] = f(obj[k]); // okay, since K extends !readonly keyof T
}
mapAssign(obj, "value", x => x+1); // error: Const.value is readonly

An alternative change (which I think is much less ergonomic) would be the following:

  • if K extends keyof T and k: K and x: T then x[k] = e would succeed, but T = {readonly [k]: any} would fail
  • if K extends readonly keyof T and k: K and x: T then x[k] = e would fail but T = {readonly [k]: any} would succeed.

So the program would be

function mapAssign<T, K extends keyof T>(obj: T, k: K, f: (v: T[K]) => T[K]) {
    obj[k] = f(obj[k]);
}
mapAssign(obj, "value", x => x+1); // error: Const.value is readonly

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions