Home > Blockchain >  No error when generic types doesn't match in TypeScript
No error when generic types doesn't match in TypeScript

Time:11-09

I wonder why I can provide different generic interface in configure() method than in my class? In the first no error example, I provide IType<Args1> as a generic type for MyClass and then I can simply override it by IArgs2 that has a prop missing and I didn't get any error. Is it any way to ensure the types are exactly the same?

interface IArgs1 {
  a: string;
  b: string;
}

interface IArgs2 {
  a: string;
}

interface IArgs3 {
  d: string;
}

interface IType<T> {
  configure(args: T): void
}

// no error - even if 'b' is missing from IArgs2
class Class implements IType<IArgs1> {
  configure(args: IArgs2) {}
}

// error - because it's missing all IArgs1 attributes
class MyClass implements IType<IArgs1> {
  configure(args: IArgs3) {}
}

CodePudding user response:

This is because T is in contravariant position. Consider this example:

interface IArgs1 {
  a: string;
  b: string;
}

interface IArgs2 {
  a: string;
}

type Covariance<T> = { box: T };

declare let args1: Covariance<IArgs1>;
declare let args2: Covariance<IArgs2>;

args1 = args2 // error
args2 = args1 // ok

As you might have noticed args2 is not assignable to args1. This is the opposite behavior.

Consider this example:

type Contravariance<T> = { box: (value: T) => void };

declare let args1:  Contravariance<IArgs1>;
declare let args2:  Contravariance<IArgs2>;

args1 = args2 // ok
args2 = args1 // error

Now, inheritance arrow has changed in opposite way. args1 is no more assignable to args2 whereas args2 is assignable to args1.

Same behavior you have in:

interface IType<T> {
  configure(args: T): void
}

since IType<T> is the same as Contravariance in variance context.

This is why you don't have an error here:

// no error - even if 'b' is missing from IArgs2
class Class implements IType<IArgs1> {
  configure(args: IArgs2) { }
}

because IArgs1 extends IArgs2

You have an error here:

// error - because it's missing all IArgs1 attributes
class MyClass implements IType<IArgs1> {
  configure(args: IArgs3) {}
}

because IArgs1 and IArgs3 are completely different types without any relation.

Here you can find more about *-variance topic

CodePudding user response:

Looks like the class's intrinsic interface "comes first" and then all the implements constraints are checked against it afterward.

This means that, in the case of MyClass, the configure(args: IArgs1): void method signature (that originates from implements IType<IArgs1>) is checked against configure(args: IArgs3): void signature (that already exists in the class), Since IArgs3 requires IArgs1 to have the d: string property, this produces a conflict and a compiler error.

Conversely, in the case of Class, IArgs2 (that already exists in the class) requires IArgs1 (that comes from implements IType<IArgs1>) to have the a: string property, – which it has, and thus no errors are produced.


Not gonna lie, this is a bit of a surprise. I'd expect to first impose all implements A, B, C constraints on a class, and then fulfill them with actual class implementation, – but TypeScript isn't written by myself.

  • Related