Simon Boudrias
Simon Boudrias

Reputation: 44589

Flow type: how to type dynamic classes factories

I apologize in advance for the vague title, but I had issue to succinctly describe my issue.

I'm having a class Table which takes a dynamic Row class with which it'll wrap the rows returned from a class method getRow(). My issue is that I cannot get it to work in flow without loosing the type returned from getRow().

Here's what I have so far:

// @flow

class Row {
    constructor() {}
};
class RowA extends Row {};
class RowB extends Row {};

class Table<RowType: Class<*>> {
    Row: RowType;

    constructor(RowClass: RowType = Row) {
        this.Row = RowClass;
    }

    getRow() {
        return new this.Row();
    }
};

const a = new Table(RowA);
(a.getRow(): RowA);

const b = new Table(RowB);
(b.getRow(): RowB);

const c = new Table();
(c.getRow(): Row);

Demo at: https://flow.org/try/#0PTAEAEDMBsHsHcBQiDG0CGBnToBKDQBvRUU0FWAO0wBcAnAVxRtjoAoBKIgX0W4G5UGbHgQBBUAFMAHjUmUAJjnzwegtFmUIAQlNnylo1YQHINIgCroARtEkAeFRYCeAB0kAuUAGFhmewBUAHxBRCRkKl5ObpKC4aQU1PRMLOwqvppRCC7uoAC8RlzEZCWgNAAWAJaYAHQq+UYZ2IIlvPGgAOaSNCqcYaVkdN0MdJSglJKqFdV1CJwtZLymqFS0oOgNE6pWtpJsKmIcgmzoNV09cxxZ8IdxiWvWm5OgO3b7OkeIbNZn3b1XRm0nxWSXIT22NjenzYKF+F3gnGuRyAA

And the error it returns:

22: (a.getRow(): RowA);
     ^ Cannot cast `a.getRow()` to `RowA` because `RowB` [1] is incompatible with `RowA` [2].
References:
17:         return new this.Row();
                   ^ [1]
22: (a.getRow(): RowA);
                 ^ [2]
25: (b.getRow(): RowB);
     ^ Cannot cast `b.getRow()` to `RowB` because `RowA` [1] is incompatible with `RowB` [2].
References:
17:         return new this.Row();
                   ^ [1]
25: (b.getRow(): RowB);
                 ^ [2]

Upvotes: 0

Views: 121

Answers (1)

Felix
Felix

Reputation: 2681

You have to implement an interface with a declared constructor or you extend a class with a (predefined) constructor. RowInterface and class Row are interchangeable int the template param for class Table.

I also added types to the getRow() method. Please see also the alternative initialization (you don't need a cast):

const a = new Table<RowA>(RowA);
let rA : RowA = a.getRow();

full code:

// @flow

interface RowInterface {
    constructor():void;
}

class Row implements RowInterface {
    constructor(){}
};

class RowA extends Row {
};
class RowB extends Row {
};

// bound to extend Row
//class Table<Entity: Row> { 
class Table<Entity: RowInterface> {
    _rowConstructor: Class<Entity>;

    // take a constructor
    constructor(row: Class<Entity>) {
        this._rowConstructor = row;
    }

    // return a constructed entity
    getRow(): Entity {
        return new this._rowConstructor();
    }
};


const a = new Table<RowA>(RowA);
let rA : RowA = a.getRow();

const b = new Table<RowB>(RowB);
(b.getRow(): RowB);

Upvotes: 1

Related Questions