简体   繁体   中英

Strange behaivour of TypeScript when extending class with similar namespace structure

Recently I've been trying to create a type library for library written in JS. When I declared all namespaces, classes and interfaces, some classes started to give me an error TS2417 . I checked if there was any problem with invalid overriding of methods or properties, but I couldn't find anything. After some time I've found that the problematic class had same name as one of the namespaces (for example class AB and namespace AB ). But this didn't cause any trouble. The problem was that the parent class (of the problematic class) had, just as the problematic class, namespace which was named exactly the same and this namespace and the namespace of the problematic class had class, which was named exactly the same, but had different interface (it is very hard for me to describe the problem, so I simulated here ).

So the question is, what causes this problem?

In this example the problematic class is A.C , but it has problems due to incompatible constructors of classes A.C.X and ABX .

declare namespace A {
  class B {

  }

  namespace B {
    class X {

    }
  }

  class C extends A.B {

  }

  namespace C {
    class X {
      constructor(x: number)
    }
  }
}

(In the following I will dispense with the namespace A )

With the exception of the construct signature , the static side of a class is checked for substitutability the same way that the instance side is; so if you have class X { static prop: string = "" } , then you can't have class Y extends X { static prop: number = 2} . When you say class Y extends X you are declaring that, among other things, Y.prop is assignable to X.prop . See microsoft/TypeScript#4628 for more information. Not everyone likes this constraint, but it's there.


That means the following should work with no error:

const X: typeof B.X = C.X; // should be okay
new X(); // should be okay

But a concrete implementation of your classes could easily lead to a runtime error:

class B {
  static X = class { }
}

class C extends B {
  static X = class {
    constructor(x: number) {
      x.toFixed();
    }
  }
}

Here, C.X is a class constructor which requires a number input. But BX is a class constructor which does not require any input at all. And if you treat the former as if it were the latter, than at runtime you will be calling the toFixed() method on undefined . Oops.

And that's why class C extends B generates a compiler error; to protect you against substitutability issues arising from the static side of a class.

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM