Capítulo 28 de 36
TypeScript compatibility is structural ("does the shape match?"), not nominal ("does it explicitly declare implementing this type?") — a design chosen because JavaScript code leans heavily on anonymous objects and function expressions, which a nominal system would fight against constantly.
x is compatible with y if y has at least the members x requires — a class or object literal never needs to declare implements SomeInterface to satisfy that interface; having the right shape is enough. The same rule governs assignment and function-call argument passing.(a: number) => void where (a: number, b: string) => void is expected) — mirroring the common JS pattern of a callback that ignores extra arguments (like Array.prototype.forEach's index/array params). The reverse (more required parameters than the target expects) is an error.{ name: string } is assignable to a target expecting { name: string; location: string }... wait, actually the reverse: a function returning the richer shape is assignable where the leaner shape is expected, not vice versa.(e: MouseEvent) => void handler where a generic (e: Event) => void is technically expected). Turn on strictFunctionTypes to make TypeScript enforce the stricter, sound direction only.private/protected members break pure structural comparison: a class with a private/protected member is only compatible with another type that has a matching private/protected member originating from the same declaration (i.e. actually inherited from the same base) — this is what makes a subclass assignment-compatible with its own superclass, while rejecting an unrelated class that merely happens to have an identically-shaped private field.interface Empty<T> {} is compatible across Empty<number>/Empty<string>); once the parameter is actually used in a member (interface NotEmpty<T> { data: T }), differing type arguments make the instantiations incompatible, exactly like two unrelated concrete types would be. For uninstantiated generic signatures (e.g. two generic function types being compared to each other), compatibility is checked by substituting any for every unspecified type parameter first.any and between enums and their underlying numbers. In practice, assignment compatibility is what governs everywhere in the language, including implements/extends clauses.any / unknown / object / void / undefined / null / never assignability, summarized:
any and unknown accept the same things (everything), but differ on the outbound side: any is assignable to anything, while unknown is assignable to nothing except any (and itself) — this asymmetry is exactly what makes unknown the safe version of any.never and unknown are near-opposites: never is assignable to everything (it represents "no value could ever be here," so it trivially satisfies any target), while nothing is assignable to never except never itself.void accepts almost nothing back and is accepted by almost nothing, except any/unknown/never — and, notably, undefined is always assignable to void regardless of strictNullChecks.strictNullChecks off, null/undefined behave close to never — assignable into nearly anything, accepting almost nothing (mutually assignable to each other, though). With it on, they instead behave close to void — not assignable to or from most things, with the same any/unknown/void exceptions.interface Pet { name: string }
class Dog { name: string = ""; }
let pet: Pet = new Dog(); // OK — Dog is never declared to `implements Pet`
Dog wherever Pet is expected purely because the shape matches, with no explicit implements relationship required.function listenEvent(handler: (e: { timestamp: number }) => void) {}
interface MouseEvent { timestamp: number; x: number; y: number }
listenEvent((e: MouseEvent) => console.log(e.x)); // allowed (bivariant params), unsound in theory, common in practice
strictFunctionTypes can optionally forbid.implements/extends to make two types compatible — compatibility comes purely from matching structure; those clauses are for documentation/checking, not for granting compatibility that shape alone wouldn't already provide.strictFunctionTypes if a codebase needs full parameter-type soundness in function comparisons — the default bivariant behavior is a deliberate, JS-pattern-motivated relaxation, not a bug.private/protected members are the one place structural typing breaks down for classes — they require a shared declaration origin, not just a matching shape, which is what makes subclass-to-superclass assignment work while blocking unrelated lookalike classes.unknown/never asymmetry: unknown accepts everything but gives back to almost nothing; never is accepted by nothing but gives back to everything.void/unknown/never/Function types referenced throughout this chapter's assignability discussion.private/protected semantics this chapter's compatibility rule depends on.