I have a strange one that I can't figure out. It works fine with Typescript before 2.7.* and it has to be some kind of related to the abstract method with generics. When I remove that abstract method, it works. It's also working if I replace the abstract generic with any, from public abstract evaluate(items: Array<TEntity>): Array<TEntity> to public abstract evaluate(items: Array<any>): Array<any>
I have no idea what to name this issue, sorry for the title.
TypeScript Version: 2.7.1
Code
enum OperatorType {
Take = 1 << 1,
Skip = 1 << 2,
}
class Operations<TEntity> {
public first<T extends Operator<TEntity>>(operator: { new (...args: any[]): T }): T {
return null;
}
}
abstract class Operator<TEntity> {
constructor(public type: OperatorType) {
}
public abstract evaluate(items: Array<TEntity>): Array<TEntity>
}
class SkipOperator<TEntity> extends Operator<TEntity> {
constructor(public count: number) {
super(OperatorType.Skip);
}
public evaluate(items: Array<TEntity>): Array<TEntity> {
return null;
}
}
interface ICar {
id: number
}
let count_failing = new Operations<ICar>().first(SkipOperator).count;
let count_working = new Operations<{}>().first(SkipOperator).count;
Expected behavior:
compile without any error
Actual behavior:
throws an error; error TS2339: Property 'count' does not exist on type 'Operator<ICar>'. even when the signature of method first indicates it should return SkipOperator instead of Operator.
Related Issues:
I'm trying different solutions to solve my issue, and I came over that extending classes and override methods with generic constraints doesn't work well.
let func: (<T extends Array<number>>(items: T) => T) = (items: Array<number>) => {
return new Array<number>();
}
The error I get is
error TS2322: Type '(items: number[]) => number[]' is not assignable to type '<T extends number[]>(items: T) => T'.
Type 'number[]' is not assignable to type 'T'.
If I change my code in first post and remove the generics and adds an interface instead it works as intended
interface IOperator<TEntity> {
evaluate(items: Array<TEntity>): Array<TEntity>
}
abstract class Operator<TEntity> implements IOperator<TEntity> {
constructor() {
}
public evaluate(items: any): any {
return null;
}
}
class SkipOperator<TEntity> extends Operator<TEntity> {
constructor(public count: number) {
super();
}
public evaluate(items: Array<TEntity>): Array<TEntity> {
return null;
}
}
But, as I mentioned in second post, it seems like constraints and type declarations isn't inherited since this doesn't work:
let skip: SkipOperator<ICar> = new Operations<ICar>().first(SkipOperator);
The error returned is
error TS2322: Type 'SkipOperator<{}>' is not assignable to type 'SkipOperator<ICar>'.
Type '{}' is not assignable to type 'ICar'.
Property 'id' is missing in type '{}'.
works fine in Typescript < 2.7
This example is better to understand where the problem really is - it has something to do with "type argument inference" that isn't solved correctly for child methods of a generic class with constraints other than primary types.
This work as it should and throws an error because of the generic constraint
let first = <T extends Number>(items: Array<T>): T => {
return items.shift();
}
let a = first([1,2,3]);
let b = first(["a","b","c"]); // error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'Number[]'
This also works as it should, it inherits TType from the generic class
class Item<TType> {
public first<T extends Array<TType>>(items: T) {
return items.shift();
}
}
let c = new Item<number>().first([1,2,3]);
let d = new Item<number>().first(["a", "b", "c"]); //error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'number[]'
But, whenever I use more complex structures as constraint it never fails
class Car {
}
let e = new Item<Car>().first([new Car(), new Car(), new Car()]);
let f = new Item<Car>().first([1,2,3]); // no error because signature of first is first<number[]> instead of first<Car[]>
The issue here is that the signature for e line is Item<Car>.first<Car[]>and for f line is Item<Car>.first<number[]>, the type TType is never assigned down to method first
@lostfields the issue is that you have an empty class (see the FAQ). Adding a property or method to the class shows an error as you would expect
@RyanCavanaugh great, then my last example works. So the issue is something with type argument inference with constructor arguments then? because this fails too:
interface ICar {
id: number
}
interface IOperator<TEntity> {
valueOf(): TEntity
}
class SkipOperator<TEntity> implements IOperator<TEntity> {
constructor(public count: number = 5) {
}
public valueOf(): TEntity {
return null;
}
}
class Collection<TEntity> {
public first<T extends IOperator<TEntity>>(operator: new () => T): T {
return null;
}
}
let count: number = new Collection<ICar>().first(SkipOperator).count
Throws error TS2339: Property 'count' does not exist on type 'IOperator<ICar>' when it should return SkipOperator<ICar> ?
If I change valueOf(): TEntity to valueOf(): number in IOperator/SkipOperator it works as intended. Both examples works in typescript 2.6.2
I was bored, so I looked into this. The behavior changed in #19345.
As far as I understand, TypeScript is matching up these two construct signatures:
SkipOperator:
new <TEntity>(count?: number): SkipOperator<TEntity>
Parameter of first:
new(): T
Before #19345, the SkipOperator signature would be erased to:
new (count?: number): SkipOperator<any>
Then T gets inferred as SkipOperator<any>, which works .
After #19345, the SkipOperator signature is replaced by its base signature with the type parameter TEntity replaced by its constraint {}:
new (count?: number): SkipOperator<{}>
Then an inference of SkipOperator<{}> is made for T, but since it doesn't satisfy the constraint IOperator<ICar>, T is set to the constraint instead.
public first<T extends { new(): IOperator<TEntity> }>(operator: T): InstanceType<T> {
which makes the sample work without error (though does go against other guidance we write...)