microsoft/TypeScript

Type checker doesn't understand string literal type in computed property key

whatisaphone opened this issue · 5 comments

TypeScript Version: nightly (2.4.0-dev.20170502)

Code

interface Thing {
  readonly foo: number;
}

function bar(t: Thing) { }

function qux(key: 'foo', value: number) {
  bar({ foo: 5 });  // works
  bar({ [key]: 5 });  // doesn't work
}

Expected behavior:

Both calls typecheck.

Actual behavior:

index.ts(9,7): error TS2345: Argument of type '{ [x: string]: number; }' is not assignable to parameter of type 'Thing'.
  Property 'foo' is missing in type '{ [x: string]: number; }'.

Duplicate of #5579

Here's a more real-world example where this would be helpful, in the context of React:

import * as React from 'react';
import { ChangeEvent } from 'react';

interface LoginState { username: string; password: string; }

export class Login extends React.Component<{}, LoginState> {
  public state: LoginState = { username: '', password: '' };

  private onChange(event: ChangeEvent<HTMLInputElement>, property: keyof LoginState) {
    this.setState({ [property]: event.target.value });  // this doesn't work!
  }

  public render() {
    return (
      <form>
        <input value={this.state.username}
               onChange={(e) => this.onChange(e, 'username')}/>
        <input type="password"
               value={this.state.password}
               onChange={(e) => this.onChange(e, 'password')}/>
        <input type="submit" value="Login"/>
      </form>
    );
  }
}

Relevant definition of setState:

class Component<P, S> {
    setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;
}

Right now the commented line in onChange does not typecheck. The compiler doesn't realize that a { [k: keyof LoginState]: string } can be assigned to a Pick<LoginState, K extends keyof LoginState>.

We currently bypass this by calling this.setState({ ...this.state, [property]: event.target.value }), but this essentially bypasses the typechecker (e.g. calling this.setState({ ...this.state, foo: 'bar' }) compiles as well). It's also passing more keys than necessary to setState, and making it do unnecessary work.

I have a similar issue.

type theProp = 'someprop';

const propName: theProp = 'someprop';

interface Some {
    someprop?: number;
}

// Correctly gives error
const literalProp: Some {
    someprop: true,
}

// Could know that the only possible value of `prop` is `someprop` and give error
const dynamicProp: Some = {
    [propName]: true,
}

This bug should be fixed. I am also having this issue. See this post on Stackoverflow: https://stackoverflow.com/questions/46361905/property-is-missing-in-type-x-string-string

This should be fixed in by #18317, give typescript@next a try.