It's worth mentioning that when using an `implements` clause, it doesn't actually alter the type of the class where it's applied. It serves as a validation mechanism to ensure the class conforms to the implemented type, but beyond this check, everything functions as if the clause is not present. Therefore, in terms of type inference,
class A implements B<string> { }
is essentially equivalent to
class A { }
This signifies that A
essentially acts as an empty class, leading to unexpected behaviors.
Specifically, when attempting to utilize conditional type inference to deduce a value for T
under the condition A extends B<T>
, it may not yield the desired outcome. If a subtype of B<T>
does not include the property bla
, then T
remains unused, resulting in failed inference. In essence, A
is considered assignable to B<T>
for any possible T
. Consequently, the compiler lacks grounds to favor one type over another, causing the inference process to default to the unknown
type:
type ThisShouldBeString = A extends B<infer T> ? T : never;
//type ThisShouldBeString = unknown 😢
To potentially resolve this issue, establishing a structural dependency on T
within the comparison between A
and
B<T></code becomes crucial. For instance:</p>
<pre><code>class A /* implements B<string> */ {
declare bla?: string;
}
In this scenario, A
possesses a structure against which it can be meaningfully contrasted with
B<T></code, even without the explicit <code>implements
clause (which has no bearing). Subsequently, successful inference occurs:
type ThisShouldBeString = A extends B<infer T> ? T : never;
//type ThisShouldBeString = string 😃
Celebrate!
Incidentally, this intricacy is independent of the generated JavaScript version of A
. Given the presence of a declare
d bla
property, it will not manifest at runtime. Both variants of A
transform into something akin to class A {}
during compilation. The distinction lies solely within the realm of static types.
Try out the code on TypeScript Playground