Typescript: Generics and keyof does not work with computed properties

Created on 6 Mar 2017  路  18Comments  路  Source: microsoft/TypeScript

TypeScript Version: 2.2.0

Code with unexpected type error:

export function set<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
    return Object.assign(obj, {
        [key]: value,
    });
}

Expected behavior:
No type errors because K should is a subtype of keyof T. It works if key: keyof T and even key: K & keyof T.

Actual behavior:
Type error:
A computed property name must be of type 'string', 'number', 'symbol', or 'any'.

Bug Fixed

Most helpful comment

This is still occurring in TS v3.3:

export async function* pick<TItem, TKey extends keyof TItem>(
    iterable: Iterable<TItem> | AsyncIterable<TItem>,
    ...keys: TKey[]
): AsyncIterable<Pick<TItem, TKey>> {
    for await (const item of iterable) {
        yield {
            [key in keys]: item[key]
        };
    }
}

Error: A computed property name must be of type 'string', 'number', 'symbol', or 'any'.

All 18 comments

I was almost desperate trying to get similar code working:

class Options<T extends object, K extends keyof T>
{
    [key:K]:T[K];

    constructor(value:T)
    {
        Object.assign(this, value);
    }
}

Also I wonder, is it possible to avoid declaring second type parameter (K)?

@kemsky You could simply replace all occurrences of K with keyof T.

Here there is another example that fails with the same error

const changeValue = <K extends keyof State> (key: K, value: string) => {
  return (prevState: State, props: Props): Pick<State, K> => {
    return {
      [key]: value
    }
  }
}

In addition to the error @altschuler gets, I get:
Type '{ [x: string]: string; }' is not assignable to type 'Pick<State, K>'

State:

interface State {
 foo: string
}

Bump, this still happens with 2.3.4, are there any plans to fix/implement this? cc @RyanCavanaugh (You're the only collaborator to interact with this issue)

The error message should be checking the apparent type of key. The apparent type would correctly resolve to the type parameter's K's constraint, which is string-like, instead of the type parameter K itself, which is not itself string-like.

Fixed in #17404. The apparent type turned out not be quite right because that also converts string to String and so on. Instead I just get the type parameter's constraint and use that if there is one.

The error from @kevinjhanna's comment is still occurring in TS 2.5 and above. Is this intended?

Yes, this needs @rbuckton's PR for binding of dynamic names to work: #15473

^ @sandersn

So, that error from @kevinjhanna's comment is still occurring in TS 2.9. Is this intended? Does it need a new issue?

Hi,
I also have a problem that may be realated to this issue (I'm using TS 2.9.2):
https://stackoverflow.com/a/50929895/2292481

Hello,
This has been implemented now but isn't in the type-system if you wanted to strongly type usages of computed properties. For example, here I'm trying to use mapped types with computed properties (I've simplified the following code/example so the purpose of the function might not make much sense practically but you can see the idea).

merge (target, source) {
    Object.keys(source).forEach(key => {
      Object.assign(target, { [key]: {} });
    });
}

And attempting to strongly typing this in an ambient module (for a JS framework) and it's currently not possible. For example:

declare function  merge<Target extends {}, Source extends {}>(target: Target, source: Source): Target extends object ?
    Source extends object ? {
      [Key in keyof Source]: Target & { [Key]: object } // Error here at [Key] - "'Key' only refers to a type, but is being used as a value here."
    } : never
  : Target;

This is still occurring in TS v3.3:

export async function* pick<TItem, TKey extends keyof TItem>(
    iterable: Iterable<TItem> | AsyncIterable<TItem>,
    ...keys: TKey[]
): AsyncIterable<Pick<TItem, TKey>> {
    for await (const item of iterable) {
        yield {
            [key in keys]: item[key]
        };
    }
}

Error: A computed property name must be of type 'string', 'number', 'symbol', or 'any'.

How to fix this TS kind error ?????

8ec7b872-5852-4c83-bef6-d716556685f7

@towry You should be able to fix that by explicitly defining the type of the indexer as a string (number, symbol or 'any') as per the error message:

let variable = {
   [a: string]: 123
};

As far as I can tell, typescript currently can't process keyof a generic when used within a computed property, and instead always converts it to string:

class Wrapper<T> {
    Inner<K extends keyof T>(key: K, value: T[K]) {
        // All good
        const x: Partial<T> = {};
        x[key] = value;

        // Type '{ [x: string]: T[K]; }' is not assignable to type 'Partial<T>'.
        const y: Partial<T> = {
            [key]: value,
        };
    }
}

https://www.typescriptlang.org/play/index.html#code/MYGwhgzhAEDqBOYAOSCm8A8AVAfNA3gFDQnQCSAdhehgNLSoAeALqhQCYwDWqAngPYAzaLgAUPXgC5otADTQAbmBABXVNKwBtWgF0AlAWKljAehNx+8LhCPGSwfhQjNojaQAUw8ZgEtl2PABeAgBfAG5bO0ZNCR1oYKVVVAjI03MsXjRoAHJ8aE03aGd4HwoAcx0NbR0w6BDs6B8YCn4XSAgfMoowACMQVGhmfkHMgezPbz8QAOyAOlTSBycXKWgJ339ceMM7XfzY6US1WQWScMiQwhCgA

You can of course just as any them to force it to work, but that's a pretty garbage solution.

@davidfarinha

灞忓箷蹇収 2019-08-02 涓嬪崍5 14 31

Still a problem in 3.6 :(

REOOOOPEEEEN TAAASK!!!!!

Was this page helpful?
0 / 5 - 0 ratings