TypeScript Version: 3.4.2
Search Terms:
regression on this type for extended generics
Code
Simplified version of original code:
// A basic structure whereby a table has records and fields
class Table<IData extends Data> {
records: IData[];
fields: Fields<IData>;
}
class Data {
s: string;
b: boolean;
_table: Table<this>;
}
class Fields<IData extends Data> {
[key:string]:Field<IData>;
}
class Field<IData extends Data> {
name: keyof IData;
}
// ----------------------------------------
class SomeData extends Data {
n: number;
}
class SomeTable extends Table<SomeData> {
}
/* ERROR blurb
Type 'SomeData' does not satisfy the constraint 'Data'.
Types of property '_table' are incompatible.
Type 'Table<SomeData>' is not assignable to type 'Table<Data>'.
Property 'n' is missing in type 'Data' but required in type 'SomeData'.
*/
Expected behavior:
Works without error in TS3.3.
Actual behavior:
Returns error as above for class SomeTable extends Table<SomeData> {}.
When _table: Table<this> is changed to _table: Table<any>, the problem goes away, but creates loss of type restrictions.
This regression may be related to this breaking change, but I can't figure out how.
Playground Link:
TS3.4 Playground @ 07 APR 2019
Related Issues:
Think the problem is described here: #30554. Specifically the problem is the name property in Field that flips the variances.
Confirmed: using name: string instead will circumvent this issue, but does not really resolve the headache.
Thanks, superman.
I have put in a bizarre self-referencing hack that shows one way to circumvent the problem with the this type:
// A basic structure whereby a table has records and fields
class Table<IData extends Data<IData>> {
records: IData[];
fields: Fields<IData>;
}
class Data<IData extends Data<IData>> {
s: string;
b: boolean;
_table: Table<IData>;
}
class Fields<IData extends Data<IData>> {
[key:string]:Field<IData>;
}
class Field<IData extends Data<IData>> {
name: keyof IData;
type: "string"|"number"|"boolean";
}
// ----------------------------------------
class SomeData extends Data<SomeData> {
n: number;
}
class SomeTable extends Table<SomeData> {
}
var st: SomeTable = new SomeTable();
// This correctly derives from Data:
st.fields.s = {
name: "s",
type: "string"
};
// This correctly derives from SomeData:
st.fields.n = {
name: "n",
type: "number"
};
// This correctly throws an error:
st.fields.wrong = {
name: "wrong",
type: "null"
};
This is a correct error from the type system's POV - keyof is contravariant, which fundamentally makes it very difficult to implement a sound class hierarchy that also uses this types. I would just replace the definition in Data with _table: Table<Data>;
Thanks for your efforts, Ryan, but this does not really solve the problem.
The mystery is that the type system does actually still do the same inferences and restrictions in the original code as it does in TS3.4, even if it does throw an error here:
class SomeTable extends Table<SomeData> {}
and yet strangely, the error can be overcome without loss of functionality by simply putting in a redundant expression like this:
class SomeTable extends Table<SomeData & Data> {}
Given that SomeData is derived from Data, I would have thought that SomeData & Data would be just SomeData.
But apparently, this is correct from the type system's POV, and given the complexities of contravariant vs covariant, I'll leave that to greater minds.
Thanks again.
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Is there any answer to the inconsistency in my previous comment? Or is this odd/broken state "working as intended" too?
Most helpful comment
Think the problem is described here: #30554. Specifically the problem is the name property in Field that flips the variances.