Reputation: 1730
Can someone explain to me why does this compile in Typescript?
class Result<T> {
object: T | null = null;
}
function setOnlyA(res: Result<{ a: number }>) {
res.object = { a: 5 };
}
function setAB(res: Result<{ a: number; b: string }>) {
setOnlyA(res);
// everything compiles fine, but res object is invalid
// at this point according to type definitions
}
I would expect setOnlyA
call to be disallowed in setAB
. I have strict
mode on. Do I need some other setting?
Upvotes: 3
Views: 522
Reputation: 1730
My fixed code looks something like this:
class Result<T> {
private object: T | null = null;
// this solves the problem
setObject = (o: T) => {
this.object = o;
};
// this doesn't
//setObject(o: T) {
// this.object = o;
//};
}
function setOnlyA(res: Result<{ a: number }>) {
res.setObject({ a: 5 });
}
function setAB(res: Result<{ a: number; b: string }>) {
setOnlyA(res);
}
i.e. the solution is to use lambda as a setter. Using regular member function doesn't work - the typescript fails to discover the problem just as in the original code.
Upvotes: 0
Reputation: 535
the system is caring that you implements the type, not that its the exact same type, think about it like an interface -
Upvotes: 0
Reputation: 250156
This is a fundamental issue with the typescript type system unfortunately. Fields are assumed to be covariant, even though a readable and writable field should actually make the type invariant. (If you want to read about covariance and contravariance, see this answer).
Ryan Cavanaugh explains in this:
This is a fundamental problem with a covariant-by-default type system - the implicit assumption is that writes through supertype aliases are rare, which is true except for the cases where it isn't.
Being very strict about field variance would probably result in a great deal of pain for users, even enabling strict variance for functions was only done for function types and not for methods, as detailed here:
The stricter checking applies to all function types, except those originating in method or construcor declarations. Methods are excluded specifically to ensure generic classes and interfaces (such as Array)
There are proposals to enable writeonly
modifiers (and be stricter about readonly
) or have explicit co/contra-variant annotations, so we might get a strict flag at a later date, but at this time this is a unsoundess/usability tradeoff the TS team has made.
Upvotes: 4
Reputation: 3604
It's OK because { a: number; b: string }
is a subtype of { a: number}
. That's how Typescript works: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#14-structural-subtyping
Upvotes: 0