TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Type narrowing with foo.constructor

Open calebegg opened this issue 8 years ago • 9 comments

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.

calebegg avatar May 23 '17 18:05 calebegg

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
    }
}

glebm avatar Oct 21 '17 19:10 glebm

@RyanCavanaugh What kind of feedback is this awaiting?

glebm avatar Oct 27 '17 08:10 glebm

What kind of feedback is this awaiting?

We would like to get hear more user requests in favor of supporting this features

mhegazy avatar Oct 27 '17 16:10 mhegazy

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.

ExE-Boss avatar Aug 28 '20 01:08 ExE-Boss

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);
  }
})()

TypeScript Playground

Such code is both concise and very readable. Wouldn't it be nice if elegance was complemented with type safety? 😢

karol-majewski avatar Nov 03 '20 00:11 karol-majewski

Is there any chance it will be fixed?

murbanowicz avatar Apr 13 '21 06:04 murbanowicz

Oh, I can add +1 to this feature request. Definitely needed in reactive programming.

ukstv avatar Sep 07 '22 16:09 ukstv

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.

matthew-dean avatar May 05 '23 14:05 matthew-dean

Bumping this up, this still doesn't seem to work…

mackuba avatar May 20 '24 19:05 mackuba