phatpaul
phatpaul

Reputation: 334

ES6 inherited function returns wrong type when invoked on subclass

When a function is inherited by a subclass, I want the return type to be as if the function were defined directly on the subclass.

To be clear, the code works fine at run-time. But I want to take advantage of static type-checking. I'm getting red squiggly lines in VScode and warnings from Google-Closure-Compiler. I'm not sure if this is an issue with my ES6 code or with my type annotations.

My trivial example ES6 classes:

// @ts-check
"use strict";

export class db_row {
    /**
     * @param {number} id 
     */
    constructor(id) {
        /** @type {number} */
        this.id = id;
    }
    clone() {
        return new db_row(this.id);
    }
}

export class Channel_row extends db_row {
    /**
     * Constructor
     * @param {*=} init
     * @param {string=} name
     * @param {string=} value
     */
    constructor(init, name, value = '') {
        let id = -1;
        if (typeof init == 'object') {
            id = init.id;
            name = init.name;
            value = init.value;
        } else if (typeof init == 'number') {
            id = init;
        }
        super(id);
        this.name = name;
        this.value = value;
    }
    clone() {
        return new Channel_row(this.id, this.name, this.value);
    }
}

export class db_table {
    /**
     * Constructor
     * @param {Array<db_row>} table 
     */
    constructor(table) {
        /**@type {Array<db_row>} */
        this.table = table;
    }
    /**
     */
    get_table_copy() { return this.table.map(item => item.clone()) }
    /**
     * @param {?number=} id 
     */
    get_row_by_id(id) {
        const row = this.table.filter(item => item.id === id)[0];
        if (row) return row.clone();
        return null;
    }
}

export class Channel_table extends db_table {
    constructor() {
        /**@type {Array<Channel_row>} */
        let table = [];
        super(table);
    }
}
// Test code below:
/**
 * 
 * @param {Channel_row} chan_row 
 */
function print_chan_row(chan_row) {
    console.log(chan_row.name);
}

let channel_table = new Channel_table();

let channel_row = channel_table.get_row_by_id(0); // hover reports that the type of channel_row is db_row, when it should be type Channel_row

print_chan_row(channel_row); // Red squiggly line error: Argument of type 'db_row' is not assignable to parameter of type 'Channel_row'.   Type 'db_row' is missing the following properties from type 'Channel_row': name, valuets(2345)

console.log(channel_row.name);  // Red squiggly line error: Property 'name' does not exist on type 'db_row'.ts(2339)

let channel_table_2 = channel_table.get_table_copy(); // hover reports that the type of channel_row is db_row[], when it should be type Channel_row[]

print_chan_row(channel_table_2[0]); // Red squiggly line error: Argument of type 'db_row' is not assignable to parameter of type 'Channel_row'.ts(2345)

Now, if I move or copy the get_row_by_id() and get_table_copy() functions into the subclass, the type errors go away. But I don't want to duplicate code unnecessarily.

How can I declare the functions in the parent class so it can be reused in child classes, but maintain the static type checking?

As a bonus, can I also generalize the clone() function so it doesn't need to be over-ridden in subclasses of db_row?

Upvotes: 1

Views: 54

Answers (0)

Related Questions