Home > Net >  Typescript generic constraint not working for instance of classes?
Typescript generic constraint not working for instance of classes?

Time:12-14

I have a parent class A, a class B child of A, and a class C child of B.

class A {}
class B extends A {}
class C extends B {}

I want to create a generic function f with its only parameter being constraint to instances of classes that extends B.

In my understanding of typescript, the function should look like below:

function f<T extends B>(p: T): any {}

However the ts type checker does not complain when I pass as parameter an instance of A or any other type (number, string, etc)

const a = f(new A()); // should complain
const b = f(new B());
const c = f(new C());
const d = f(123); // should complain

Am I missing something obvious? Is it a problem of configuration of the type checker?

CodePudding user response:

Typescript has structural typing. What the compiler checks is the structure, not the actual (nominal) type.

Since your types are empty (have no members) they are satisfied by much more types than you expect.

However, add members to your types (e.g. a member to B that is not present in A) so that the compiler can check the structure and your constraint is checked, as you'd expect

class A {
  a!: string;
}
class B extends A {
  b!: string;
}
class C extends B {}

function f<T extends B>(p: T): any {}

const a = f(new A()); // complains
const b = f(new B());
const c = f(new C());
const d = f(123); // complains

CodePudding user response:

You do not need generics for this. Generics is for creating different types of functions out of your generic type. Here you are only looking for a certain type of function that only accepts instances of B.

Also you better define some properties in your classes otherwise all empty class instance will be assignable to empty class instances. This github issue has more info. The reason seems to be Structural Type system.

A structural type system (or property-based type system) is a major class of type systems in which type compatibility and equivalence are determined by the type's actual structure or definition and not by other characteristics such as its name or place of declaration.

Here is the wiki for this

You can look at this to understand:

class A {
      private a: number
}
class B extends A {
    private b: number
}
class C extends B {
    private c: number
}

function f(x : B): any {


}

const a = f(new A()); // should complain
const b = f(new B());
const c = f(new C());
const d = f(123); // should complain

Link (Please ignore the non-use warning in the playground. That is not related to the question)

Notice how if you remove the properties from the classes, the errors disappear.

PS: You can still use generics here like:

class A {
      private a: number
}
class B extends A {
    private b: number
}
class C extends B {
    private c: number
}

function f<P extends B>(x : P): any {


}

const a = f(new A()); // should complain
const b = f(new B());
const c = f(new C());
const d = f(123); // should complain

Link

  • Related