The behavior described is the desired outcome and it maintains type safety measures. To elaborate on this concept, let's modify the types by using function-valued properties (e.g., foo: ()=>void
) instead of methods (e.g., foo(): void
). Additionally, we will apply the --strictFunctionTypes compiler flag:
interface System {
Update: (e: Transform & RenderData) => void
}
class RenderSystem implements System {
Update = (e: RenderData) => { } // valid
}
class BadRenderSystem implements System {
Update = (e: number) => { } // error!
// Type 'Transform & RenderData' cannot be assigned to type 'number'
}
In terms of type safety, we are primarily concerned with substitutability. In simple terms, if a value of type X
can seamlessly replace a value of type Y
, then X
is considered a subtype of
Y</code.</p>
<p>This principle requires that function types exhibit <a href="https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance" rel="nofollow noreferrer"><em>contravariant</em></a> behavior in their parameter types. Essentially, the subtype relationship between function types operates inversely to the relationship between their parameters.</p>
<p>TypeScript enforces this type safety rule when <code>--strictFunctionTyes
is enabled for non-method types.
Therefore, in the given context, the implementation of RenderSystem
is appropriate as its Update
method accepts input of type RenderData
, which is a supertype of Transform & RenderData
.
You can verify this substitutability scenario by pretending I request a System
instance and you provide an object named system
of type RenderSystem
. If I treat it as a System
and pass an object e
of type Transform & RenderData
calling system.Update(e)
, it works because anything the RenderSystem.Update
method does with
e</code remains within the bounds of both <code>Transform
and
RenderData
.
In contrast, consider the case where I receive a value from BadRenderSystem
. When I attempt to execute system.Update(e)
, the BadRenderSystem
's Update
method treats e
as a number
, which typically leads to errors since Transform & RenderData
cannot function as a number
. Hence, BadRenderSystem
fails the substitutability test for System
.
In summary, everything adheres to the expected standards in this scenario.
This concludes the response to the initial query, although you might question why the type definitions were altered earlier.
Interestingly, TypeScript permits unsafe practices with method implementations. By disabling --strictFunctionTypes
or sticking to method syntax, TypeScript allows covariance in method types. Covariance implies that method parameter inputs align either as supertypes or subtypes with the extended or implemented methods.
Bivariant checking of method parameters isn't entirely safe but offers convenience. You can delve into the rationale behind this approach in the FAQ section or through the provided link on "bivariantly".
While not directly pertinent to the displayed code snippet, switching back to methods reveals that UnsafeRenderSystem
is now acceptable due to bivariant parameter checks, unlike BadRenderSystem
which still violates type compatibility conventions.
This distinction may be worth remembering for future practical scenarios.
Access the Playground link here