Typescript: Method argument types aren't inferred from mapped types

Created on 5 Jun 2018  路  6Comments  路  Source: microsoft/TypeScript


TypeScript Version: master


Search Terms:

Code

declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
f({ data: 0 }, {
  data(value, key) {
  },
});

Should also work with optional.

declare function f<T extends object>(data: T, handlers: { [P in keyof T]?: (value: T[P], prop: P) => void; }): void;
f({ data: 0 }, {
  data(value, key) {
  },
});

Expected behavior:

pass

Actual behavior:

$ node built/local/tsc.js --strictNullChecks --noImplicitAny index.ts
index.ts:3:8 - error TS7006: Parameter 'value' implicitly has an 'any' type.

3   data(value, key) {
         ~~~~~


index.ts:3:15 - error TS7006: Parameter 'key' implicitly has an 'any' type.

3   data(value, key) {
                ~~~

Playground Link:

Related Issues:

Needs Investigation

Most helpful comment

There, I fixed it: #27586.

All 6 comments

@weswigham Can you take a look? Data binding is popular usage.

What I believe is the same issue came up again in this StackOverflow question.

I have a patch that fixes this issue but is causing other test failures. Working on it...

There, I fixed it: #27586.

Our codebase runs into this compiler limitation. While investigating the root cause, I came up with a bunch of cases that I expected all to compile without errors, but half fail because of the lack of contextual typing as described in the OP.

I'll paste my repro here in case it's useful to add to the compiler tests (eg in @mattmccutchen's PR).

type TakeString = (s: string) => any;

// Various functions accepting an object whose properties are TakeString functions.
// Note these all use mapped types.
declare function mapped1<T extends {[P in string]: TakeString}>(obj: T): void;
declare function mapped2<T extends {[P in keyof T]: TakeString}>(obj: T): void;
declare function mapped3<T extends {[P in keyof any]: TakeString}>(obj: T): void;
declare function mapped4<T>(obj: T & {[P in keyof T]: TakeString}): void;
declare function mapped5<T, K extends keyof T>(obj: T & {[P in K]: TakeString}): void;
declare function mapped6<K extends string>(obj: {[P in K]: TakeString}): void;
declare function mapped7<K extends keyof any>(obj: {[P in K]: TakeString}): void;
declare function mapped8<K extends 'foo'>(obj: {[P in K]: TakeString}): void;
declare function mapped9<K extends 'foo'|'bar'>(obj: {[P in K]: TakeString}): void;

// Expected: no errors. Actual: half work, half don't.
mapped1({foo: s => 42});    // OK: 's' is contextually typed as 'string'
mapped2({foo: s => 42});    // ERROR: Parameter 's' implicitly has an 'any' type
mapped3({foo: s => 42});    // OK: 's' is contextually typed as 'string'
mapped4({foo: s => 42});    // ERROR: Parameter 's' implicitly has an 'any' type
mapped5({foo: s => 42});    // OK: 's' is contextually typed as 'string'
mapped6({foo: s => 42});    // ERROR: Parameter 's' implicitly has an 'any' type
mapped7({foo: s => 42});    // OK: 's' is contextually typed as 'string'
mapped8({foo: s => 42});    // ERROR: Parameter 's' implicitly has an 'any' type
mapped9({foo: s => 42});    // OK: 's' is contextually typed as 'string'

Playground link

Today, language service provides correct info:

(property) data: (value: number, prop: "data") => void

But oddly it is not reflected in type inference and type checking. @weswigham @ahejlsberg Can you infer types like that info? I'm not sure why such difference is allowed. #29409 is another similar case.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wmaurer picture wmaurer  路  3Comments

zhuravlikjb picture zhuravlikjb  路  3Comments

bgrieder picture bgrieder  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments

jbondc picture jbondc  路  3Comments