More accurate typing of Object.assign and React component setState()

This is a proposal for solving the second use case mentioned in Issue #6218

Problem

/**
 * This class is a given by the React framework
 */
class ReactComponent<S> {
    protected _state: S;

    /**
     * This method actually accepts any supertype of S
     */
    protected setState(newState: S): void {
        for (let name in newState) {
            if (newState.hasOwnProperty(name)) {
                this._state[name] = newState[name];
            }
        }
    }

    protected componentWillMount(): void {
        // abstract
    }
}

/**
 * Some state interface declaration. Note all members are optional to allow setState to
 * be called with supertypes of BaseState
 */
interface BaseState {
    a?: string;
}

/**
 * My own base class for certain React widgets
 */
class BaseWidget<S extends BaseState> extends ReactComponent<S> {

    constructor() { 
        super();
        this._state = {};
    }

    protected componentWillMount(): void {
        this.setState({ a: "boo" });
    }
} 
$ tsc v1.ts
v1.ts(39,9): error TS2322: Type '{}' is not assignable to type 'S'.
v1.ts(43,23): error TS2345: Argument of type '{ a: string; }' is not assignable to parameter of type 'S'.

The compiler cannot know that the setState() method will accept any super-type of S, i.e. an object with any subset of the members of S.

Proposal

Add a keyword to type setState() properly. To avoid confusion, I chose partof instead of e.g. 'supertype of' (see also initial confusion in #6218). For me, 'partof' conveys that I can give the method any object with a subset of the members of S.

/**
 * This class is a given by the React framework
 */
class ReactComponent<S extends {}> {
    protected _state: S;

    /**
     * This method accepts any supertype of S
     */
    protected setState(newState: partof S): void {
    }
}

Properties of partof

  • Only works for object types (hence the 'extends {}' above) because I wouldn't know how to pass part of e.g. a string
  • Allows any supertype of the given type
  • Incorporates knowledge of the generic parameter. Given the class declaration class ReactComponent<S extends { foo: number; }>, one is able to call setState({ foo: 3}) and setState({}) but not setState({ bar: 3 }) inside the class definition.

Comments more than welcome, I'm not a compiler expert. This is just to get the discussion going. If you think there already exists a way of typing this, please check with the original issue for a couple of failed examples.