TypeScript Version: 3.3.3333
Search Terms:
Code
interface IFoo<T> {
test<T2, P extends keyof T2>(obj: T2, property: P): void;
}
class Foo<T> implements IFoo<T> {
test<T2, P extends keyof T2>(obj: T2, property: P): void {
}
}
Expected behavior:
No error
Actual behavior:
error TS2416: Property 'test' in type 'Foo<T>' is not assignable to the same property in base type 'IFoo<T>'.
Type '<T2, P extends keyof T2>(obj: T2, property: P) => void' is not assignable to type '<T2, P extends keyof T2>(obj: T2, property: P) => void'. Two different types with this name exist, but they are unrelated.
Types of parameters 'property' and 'property' are incompatible.
Type 'P' is not assignable to type 'keyof T2'.
Type 'keyof T2' is not assignable to type 'keyof T2'. Two different types with this name exist, but they are unrelated.
Type 'string | number | symbol' is not assignable to type 'keyof T2'.
Type 'string' is not assignable to type 'keyof T2'.
The following works with no error:
interface IFoo<T> {
test<T2, P extends keyof T2>(obj: T2, property: P): void;
test<T2, P extends keyof T2>(obj: T2, property: P): void;
}
class Foo<T> implements IFoo<T> {
test<T2, P extends keyof T2>(obj: T2, property: P): void {
}
}
I found what I think is the same bug, here is my alternative minimized repro:
https://www.typescriptlang.org/play/#src=interface%20I%3CT%3E%20%7B%0D%0A%09Foo%3F(t%3A%20T)%3A%20void%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20X%20%7B%0D%0A%09Foo%3CT%20extends%20I%3Cobject%3E%3E(c%3A%20T%20%7C%20I%3CT%3E)%3A%20c%20is%20T%3B%0D%0A%7D%0D%0A%0D%0Aclass%20XX%20implements%20X%20%7B%0D%0A%09Foo%3CT%20extends%20I%3Cobject%3E%3E(c%3A%20T%20%7C%20I%3CT%3E)%3A%20c%20is%20T%20%7B%0D%0A%09%09return%20false%3B%0D%0A%09%7D%0D%0A%7D%0D%0A
interface I
Foo?(t: T): void;
}
interface X {
Foo
}
class XX implements X {
Foo
return false;
}
}
Using Tsc Version 3.4.3.
Another related test case: http://www.typescriptlang.org/play/#code/C4TwDgpgBA0hIB4AqUIA9gQHYBMDOUewATgJZYDmUAPlAN4C+AfFALxQrqa4FFmVQA-BygAuKAGt4AewBmHANwBYAFCrymYrICGAY2gAhbXgjJUGbPkIlyVWoxZ1VUKLOnSAFNuIU84uIhITACU4gBu0qQ4yioMqqq6ADbGBADq0sQStqlRFBDAZlyWvDYC9sxQpAC2YIkQVdjABEYmyI7Oru5ePn6w8G2hUBFR9HGx8SpJKVAGxNJSWDk4eQWcFjzW-HajLNW19Y3NxqZr3FaMQiLiQfQdbp7evv79QYPDOKOqDEA
type Key<T extends string | {}> = T extends string ? T : keyof T;
interface Base<T extends string | {}> {
foo(args: Key<T>): void;
}
class WorkingWidget<T extends string | {}> implements Base<T> {
foo(args: Key<T>): void {}
}
class BrokenWidget<T extends string | {}> implements Base<T extends {} ? T : T> {
foo(args: Key<T>): void {}
}
Error:
Property 'foo' in type 'BrokenWidget<T>' is not assignable to the same property in base type 'Base<T extends {} ? T : T>'.
Type '(args: Key<T>) => void' is not assignable to type '(args: Key<T extends {} ? T : T>) => void'.
Types of parameters 'args' and 'args' are incompatible.
Type 'Key<T extends {} ? T : T>' is not assignable to type 'Key<T>'.
Type 'keyof (T extends {} ? T : T)' is not assignable to type 'keyof T'.
Type 'string | number | symbol' is not assignable to type 'keyof T'.
Type 'string' is not assignable to type 'keyof T'.
Type 'string' is not assignable to type 'never'.
Type 'keyof (T extends {} ? T : T)' is not assignable to type 'never'.
Type 'string | number | symbol' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.
This issue still exists on TypeScript 3.7.2: Playground link
Compiler Options:
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"useDefineForClassFields": false,
"alwaysStrict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"downlevelIteration": false,
"noEmitHelpers": false,
"noLib": false,
"noStrictGenericChecks": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"esModuleInterop": true,
"preserveConstEnums": false,
"removeComments": false,
"skipLibCheck": false,
"checkJs": false,
"allowJs": false,
"experimentalDecorators": false,
"emitDecoratorMetadata": false,
"target": "ES2017",
"module": "ESNext"
}
}
Input:
interface Model<S, PK extends keyof S> {
join<S2, PK2 extends keyof S2>(s: S2, pk: PK2): void;
}
class Table<S, PK extends keyof S> implements Model<S, PK> {
public join<S2, PK2 extends keyof S2>(s: S2, pk: PK2): void {
// Dummy implementation
}
}
This produces an error claiming that the method is incompatible with the one in the interface even though the signatures are identical:
Property 'join' in type 'Table<S, PK>' is not assignable to the same property in base type 'Model<S, PK>'.
Type '<S2, PK2 extends keyof S2>(s: S2, pk: PK2) => void' is not assignable to type '<S2, PK2 extends keyof S2>(s: S2, pk: PK2) => void'. Two different types with this name exist, but they are unrelated.
Types of parameters 'pk' and 'pk' are incompatible.
Type 'PK2' is not assignable to type 'keyof S2'.
Type 'keyof S2' is not assignable to type 'keyof S2'. Two different types with this name exist, but they are unrelated.
Type 'string | number | symbol' is not assignable to type 'keyof S2'.
Type 'string' is not assignable to type 'keyof S2'.
I see this issue in TS 3.7.4 as well. If I add another signature, i.e. overloading, then TS is happy.
I am using 3.7.5. Having this error.
class Table {
async get_all(user_making_request) {
// parent implementation
}
}
class User extends Table {
async get_all(user_making_request, options) {
// child implementation
}
}
Got error in child.
Property 'get_all' in type 'User' is not assignable to the same property in base type 'Table'.
Type '(user_making_request: any, options: any) => Promise' is not assignable to type '(user_making_request: any) => Promise '.ts(2416)
it does not make sence
Same issue. how can we address?
@wannadream That error makes sense to me. Let's say you have:
const user: Table = new User();
if you call user.get_all(123) (which is defined as a Table), options will be unset; hence, the compiler complains so that you don't end in such a situation. For more details, read up on strict function types and contravariance here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html
I don't think your example explains the OOP concept. If we overload certain methods, we definitely won't do
const entity: Parent = new Child();
but
const entity = new Child();
At least, the TS transpiler should not complain about it as that should be a RUNTIME error in your example. The class itself has nothing wrong, I would say.
When you overload a method, the overload needs to be compatible with the signature of that method in the extended class. My example was just shorthand for showing that if you have a User, it can be passed into any place that expects a Table, including an assignment.
The goal of TS is to reduce runtime errors, so it's correct in flagging a bad overload as a compile-time error. This issue is specifically about TS being overzealous with flagging generic overloads, not overloads in general having the wrong arity.
got the same error when using generic abstract class which I really do not understand. The only way to avoid the message is to type the parameter as any in the absctract class which is not efficient.
exemple :
Abstract class
export abstract class AbsClsCRUDRepository<TObjectType> {
//implements ICRUDOptions<TObjectType>
private _fonctionToExecute: HighOrderFunctionHandler<
any,
Promise<IGReturnData<TObjectType>>
>;
constructor(
functionToExecute: HighOrderFunctionHandler<
any,
Promise<IGReturnData<TObjectType>>
>
) {
this._fonctionToExecute = functionToExecute;
}
protected async getAll<TParamsType>( <--changing TParamsType by any makes compiler happy
parametersToFunction: TParamsType <--changing TParamsType by any makes compiler happy
): Promise<IGReturnData<TObjectType>> {
const resu = await this._fonctionToExecute.call(parametersToFunction);
return resu;
}
}
class the implements the abstract one
type TParams = IPaginationArgs & IOrderByArgs & { ids: Array<string> };
export class ClsBudgetClass extends AbsClsCRUDRepository<IBudgets> {
public async getAll(parameters: TParams) { <--compiler complains here
return await super.getAll(parameters);
}
}
compiler error :
Property 'getAll' in type 'ClsBudgetClass' is not assignable to the same property in base type 'AbsClsCRUDRepository<IBudgets>'.
Type '(parameters: TParams) => Promise<IGReturnData<IBudgets>>' is not assignable to type '<TParamsType>(parametersToFunction: TParamsType) => Promise<IGReturnData<IBudgets>>'.
Types of parameters 'parameters' and 'parametersToFunction' are incompatible.
Type 'TParamsType' is not assignable to type 'TParams'.
Type 'TParamsType' is not assignable to type 'IPaginationArgs'
Turns out typescript type checks properties different than methods (it impacts variance of generic types differently), and you happen to be able to work around this by changing the member of the interface to a property instead of a method:
interface IFoo<T> {
test: <T2, P extends keyof T2>(obj: T2, property: P) => void;
}
class Foo<T> implements IFoo<T> {
test<T2, P extends keyof T2>(obj: T2, property: P): void {
}
}
I had the same issue,
I had to remove those lines :
"strictFunctionTypes": true,
"strictBindCallApply": true
in my tsconfig.json file
Same issue with simple subclassing:
class A {
protected foo?: string;
}
class B extends A {
protected foo?: string | null;
}
@bendavis78 that isn't a bug
Most helpful comment
I found what I think is the same bug, here is my alternative minimized repro:
https://www.typescriptlang.org/play/#src=interface%20I%3CT%3E%20%7B%0D%0A%09Foo%3F(t%3A%20T)%3A%20void%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20X%20%7B%0D%0A%09Foo%3CT%20extends%20I%3Cobject%3E%3E(c%3A%20T%20%7C%20I%3CT%3E)%3A%20c%20is%20T%3B%0D%0A%7D%0D%0A%0D%0Aclass%20XX%20implements%20X%20%7B%0D%0A%09Foo%3CT%20extends%20I%3Cobject%3E%3E(c%3A%20T%20%7C%20I%3CT%3E)%3A%20c%20is%20T%20%7B%0D%0A%09%09return%20false%3B%0D%0A%09%7D%0D%0A%7D%0D%0A
interface I {
Foo?(t: T): void;
}
interface X {>(c: T | I): c is T;
Foo
}
class XX implements X {>(c: T | I): c is T {
Foo
return false;
}
}
Using Tsc Version 3.4.3.