Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Property is not assignable to the same property in generic base type #30071

Closed
jjvainav opened this issue Feb 24, 2019 · 19 comments
Closed

Property is not assignable to the same property in generic base type #30071

jjvainav opened this issue Feb 24, 2019 · 19 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@jjvainav
Copy link

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'.

Playground Link

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 {
    }
}
@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Feb 26, 2019
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Mar 14, 2019
@CraigMacomber
Copy link

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<T extends I>(c: T | I): c is T;
}

class XX implements X {
Foo<T extends I>(c: T | I): c is T {
return false;
}
}

Using Tsc Version 3.4.3.

@oleg-codaio
Copy link

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'.

@ktkiiski
Copy link

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'.

@ujwal-setlur
Copy link

I see this issue in TS 3.7.4 as well. If I add another signature, i.e. overloading, then TS is happy.

@wannadream
Copy link

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)

@lovetingyuan
Copy link

it does not make sence

@Nexeuz
Copy link

Nexeuz commented Feb 21, 2020

Same issue. how can we address?

@oleg-codaio
Copy link

@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

@wannadream
Copy link

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.

@oleg-codaio
Copy link

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.

@jeromeSH26
Copy link

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'

@CraigMacomber
Copy link

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 {
    }
}

Playground Link

@dlaweb
Copy link

dlaweb commented Apr 21, 2020

I had the same issue,
I had to remove those lines :

"strictFunctionTypes": true,
"strictBindCallApply": true

in my tsconfig.json file

@bendavis78
Copy link

Same issue with simple subclassing:

class A {
    protected foo?: string;
}

class B extends A {
    protected foo?: string | null;
}

Playground Link

@RyanCavanaugh
Copy link
Member

@bendavis78 that isn't a bug

@RamoFX
Copy link

RamoFX commented Feb 4, 2021

Same issue.

// types
interface componentApi {
  name: 'vue' | 'react',
  entryExt: string
}

interface config {
  script: string,
  style: string,
  componentApi?: componentApi
}

// main
class Config implements config {
  public readonly script = 'ts'
  public readonly style = 'scss'
  //public readonly componentApi = { // not okay
  /*
  Property 'componentApi' in type 'Config' is not assignable to the same property in base type 'config'.
    Type '{ name: string; entryExt: string; }' is not assignable to type 'componentApi'.
      Types of property 'name' are incompatible.
        Type 'string' is not assignable to type '"vue" | "react"'.
  */
  public readonly componentApi: componentApi = { // okay
    name: 'react',
    entryExt: `${ this.script }x`
  }
}
export default new Config()

@RyanCavanaugh
Copy link
Member

@nhhockeyplayer this issue is not a place for freeform complaints; please stay on-topic

@webstrand
Copy link
Contributor

The bug in the original post seems to be fixed as of v4.3.5 playground

@RyanCavanaugh
Copy link
Member

Thanks @webstrand !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

No branches or pull requests