Type narrowing with foo.constructor
TypeScript Version: 2.3.1
Code
class FooTask {
foo: number;
}
class BarTask {
bar: number;
}
type taskType = FooTask|BarTask;
function createEvent(task: taskType) {
if (task instanceof FooTask) {
task.foo; // Works.
}
switch (task.constructor) {
case FooTask:
task.foo; // Fails.
}
}
It would be nice if the second worked like the first; narrowed the type of task appropriately.
At first I though this was because T.constructor is not of type T (#3841), but looks like that's not the only issue:
class A {
'constructor': typeof A;
a() { }
}
class B {
'constructor': typeof B;
b() { }
}
function f(x: A | B) {
switch (x.constructor) {
case A:
x.a(); // Fails: not narrowed
case B:
x.b(); // Fails: not narrowed
}
}
@RyanCavanaugh What kind of feedback is this awaiting?
What kind of feedback is this awaiting?
We would like to get hear more user requests in favor of supporting this features
You need to add break statements:
function f(x: A | B) {
switch (x.constructor) {
case A:
x.a();
+ break;
case B:
x.b();
+ break;
}
}
Also, another issue is that TypeScript doesn’t do narrowing using === on objects.
I needed it a few times now. A use case from today: applying a strategy based on a runtime type.
let whitelist: string[] | RegExp | ((location: Location) => boolean);
let path = '/'
whitelist = [path];
((): boolean => {
switch (whitelist.constructor) {
case Array:
return whitelist.includes(path);
case RegExp:
return path.match(whitelist) !== null;
case Function:
return whitelist(location);
}
})()
Such code is both concise and very readable. Wouldn't it be nice if elegance was complemented with type safety? 😢
Is there any chance it will be fixed?
Oh, I can add +1 to this feature request. Definitely needed in reactive programming.
For some reason I thought TypeScript already had a feature of type narrowing via constructor, and it sort of does, but today I tried:
const images = this.productImages // this is string | Map<string, string>
if (images.constructor === String) {
return images // TypeScript correctly identifies this as a string
}
return images.has(ImageConstants.Large). // TS error: Property 'has' does not exist on type 'string | Map<string, string>'
? images.get(ImageConstants.Large)
: images.get(ImageConstants.Medium)
I take it that's because there are edge cases where a string can have a constructor from something other than String, but it would be nice to be able to tell TypeScript that that's never going to happen.
Bumping this up, this still doesn't seem to work…