Inherited typing for class property initializers
See original GitHub issueProblem
Initializing a class member with things like { }
, null
, undefined
, or []
has unexpected behavior.
class Base {
favorites = ["red", "blue"];
}
class Derived extends Base {
favorites = [];
constructor() {
this.favorites.push('green'); // Can't push string onto never[], wat?
}
}
interface Settings {
size?: number;
color?: string;
}
class Base {
settings: Settings = { size: 42 };
}
class Derived extends Base {
settings = { };
constructor() {
if (big) this.settings = { siz: 100 }; // no error, wat?
}
}
Solution
New rule: When a class property is initialized with exactly null
, undefined
, { }
, or []
, the type of the property is taken from the same property of the inherited type (if one exists), rather than the type of the initializer.
The inherited type is B & I1 & I2 & ...
where B
is the base class and I1
, I2
, ...
are the implement
ed interfaces of the class.
Examples
interface Positionable {
position: string | null;
}
class MyPos implements Positionable {
position = null;
setPos(x: string) {
this.position = x;
}
getPos() {
return this.position.subtr(3); // error detected
}
}
class Base {
items = ['one'];
}
class Derived extends Base {
items = []; // no longer an implicit any
}
var x = new Derived();
x.items.push(10); // Error as expected
Bad Ideas We Thought Were good
Contextual typing plays poorly with other behavior such as unit type positions. Consider
enum E { A, B, C }
class Base {
thing = E.A;
}
class Derived extends Base {
thing = E.B;
change() {
this.thing = E.C; // Error! wat
}
}
This turns into a big problem because the E.B
expression is contextually typed by the unit-like type E.A | E.B | E.C
and so acquires the specific type E.B
rather than the intended type E
! Daniel found this break in Azure.
/cc conspirators @DanielRosenwasser @sandersn
Issue Analytics
- State:
- Created 7 years ago
- Reactions:41
- Comments:22 (12 by maintainers)
Top GitHub Comments
This issue has been open since 2016, any updates?
Is there any update on this issue? I am eager to see it in the closest releases.
Regarding this feedback:
Of course, there may be some breaking changes, but those will appear in bad code (with implicit
any
) and some of them can be fixed, like setting the type of the property by its initializer if it is assignable to the type from base/implemented interface. I think that this feature will bring more advantages (like less boilerplate and narrowing the sources of truth throughout the types of TypeScript apps).