[Proposal] Type assertion statement (type cast) at block-scope level
See original GitHub issueThis is a proposal in order to simplify the way we have to deal with type guards in TypeScript in order to enforce the type inference.
The use case is the following. Let us assume we have dozens (and dozens) of interfaces as the following:
Code
interface AARect {
x: number; // top left corner
y: number; // top left corner
width: number;
height: number;
}
interface AABox {
x: number; // center
y: number; // center
halfDimX: number;
halfDimY: number;
}
interface Circle {
x: number; // center
y: number; // center
radius: number;
}
// And much more...
And we have a union type like this one:
type Geometry = AARect | AABox | Circle | // ... And much more
It is quite easy to discriminate a type from another with hasOwnProperty
or the in
keyword:
function processGeometry(obj: Geometry): void {
if ("width" in obj) {
let width = (obj as AARect).width;
// ...
}
if ("halfDimX" in obj) {
let halfDimX = (obj as AABox).halfDimX;
// ...
}
else if ("radius" in obj) {
let radius = (obj as Circle).radius;
// ...
}
// And much more...
}
But, as we can see, this is quite burdensome when we need to manipulate obj
inside each if
block, since we need to type cast each time we use obj
.
A first way to mitigate this issue would be to create an helper variable like this:
if ("width" in obj) {
let helpObj = obj as AARect;
let width = helpObj.width;
// ...
}
But this is not really satisfying since it creates an artefact we will find in the emitted JavaScript file, which is here just for the sake of the type inference.
So another solution could be to use user-defined type guard functions:
function isAARect(obj: Geometry): obj is AARect {
return "width" in obj;
}
function isAABox(obj: Geometry): obj is AABox {
return "halfDimX" in obj;
}
function isCircle(obj: Geometry): obj is Circle {
return "radius" in obj;
}
// And much more...
function processGeometry(obj: Geometry): void {
if (isAARect(obj)) {
let width = obj.width;
// ...
}
if (isAABox(obj)) {
let halfDimX = obj.halfDimX;
// ...
}
else if (isCircle(obj)) {
let radius = obj.radius;
// ...
}
// And much more...
}
But again, I find this solution not really satisfying since it still creates persistent helpers functions just for the sake of the type inference and can be overkill for situations when we do not often need to perform type guards.
So, my proposal is to introduce a new syntax in order to force the type of an identifier at a block-scope level.
function processGeometry(obj: Geometry): void {
if ("width" in obj) {
assume obj is AARect;
let width = obj.width;
// ...
}
if ("halfDimX" in obj) {
assume obj is AABox;
let halfDimX = obj.halfDimX;
// ...
}
else if ("radius" in obj) {
assume obj is Circle;
let radius = obj.radius;
// ...
}
// And much more...
}
Above, the syntax assume <identifier> is <type>
gives the information to the type inference that inside the block, following this annotation, <identifier>
has to be considered as <type>
. No need to type cast any more. Such a way has the advantage over the previous techniques not to generate any code in the emitted JavaScript. And in my opinion, it is less tedious than creating dedicated helper functions.
This syntax can be simplified or changed. For instance we could just have :
<identifier> is <obj>
without a new keyword assume
, but I am unsure this would be compliant with the current grammar and design goals of the TypeScript team.
Nevertheless, whatever any welcomed optimization, I think the general idea is relevant for making TypeScript clearer, less verbose in the source code and in the final JavaScript, and less tedious to write when we have to deal with union types.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:104
- Comments:76 (31 by maintainers)
Top GitHub Comments
Technically we could just consider
in
as a form of type guards.But I can still imagine something that says “assume the type of this entity is so-and-so for this block”.
how about adding the typeguard after the
if
condition itself by re-using the existing typeguard syntax: