Skip to content

proposal: type guards by constructor signature #1283

Closed

Description

What does we need?

We need type guards by constructor signature.

// class decomposed. from http://www.typescriptlang.org/Handbook#writing-dts-files
interface A_Static {
    new(m: any): A_Instance;
    st: string;
}
interface A_Instance {
    inst: number;
}
declare var A: A_Static;

// use type guards
var obj: A_Instance | Date;
if(obj instanceof A) {
  // I hope obj is narrowing to A_Instance. but A_Static does not have `prototype` property.
  obj.inst;
}

Motivation

almost .d.ts files does not have prototype property in DefinitelyTyped.
It means almost .d.ts files is not union types ready.
I think We can't enforce implement prototype property to .d.ts files.

Proposal

https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#420-type-guards

A type guard of the form x instanceof C, where C is of a subtype of the global type 'Function' and C has a property named 'prototype'

modified to

  • A type guard of the form x instanceof C, where C is of a subtype of the global type 'Function' and C has property named 'prototype' or C has a constructor signature.
    • when true,
      • narrows the type of x to the type of the 'prototype' property in C provided it is a subtype of the type of x, or
      • narrows the type of x to the type of the constructor signature's return type in C provided it is a subtype of the type of x, or

My english is not so good. I made sample code.

// only prototype
interface AStatic {
  (): void;
  prototype: AInstance;
}
interface AInstance {
  a: string;
}
declare var A: AStatic;
var objA: AInstance | Date;
if(objA instanceof A) {
  // narrows to AInstance
  // this pattern already possible!
  objA.a;
}

// only constructor signature
// this pattern is very popular in DefinitelyTyped.
interface BStatic {
  new (): BInstance;
}
interface BInstance {
  b: string;
}
declare var B: BStatic;
var objB: BInstance | Date;
if(objB instanceof B) {
  // narrows to BInstance
  objB.b;
}

// prototype + constructor signature
interface CStatic {
  new (): C1Instance;
  prototype: C2Instance;
}
interface C1Instance {
  c1: string;
}
interface C2Instance {
  c2: string;
}
declare var C: CStatic;
var objC: C1Instance | C2Instance | Date;
if(objC instanceof C) {
  // narrows to C2Instance. prototype is high priority
  objC.c2;
}

// constructor signature conflicted
interface DStatic {
  new (): D1Instance;
  new (value: any): D2Instance;
}
interface D1Instance {
  d1: string;
}
interface D2Instance {
  d2: string;
}
declare var D: DStatic;
declare var objD: D1Instance | D2Instance | Date;
if(objD instanceof D) {
  // narrows to D1Instance | D2Instance. union type of construtor signature's return types.
  (<D1Instance>objD).d1;
  (<D2Instance>objD).d2;
  // I hope this line to be error.
  (<Date>objD).getTime();
}

Activity

  1. RyanCavanaugh commented on Nov 26, 2014

    @RyanCavanaugh
    Member

    I definitely get the motivation here.

    One issue is that construct signatures can be generic and/or overloaded. We could probably use the union type of all the construct signatures' return types with {} filling in the type parameters.

  2. Igorbek commented on Nov 26, 2014

    @Igorbek
    Contributor

    It's definitely very useful suggestion. 👍

  3. johnnyreilly commented on Nov 26, 2014

    @johnnyreilly

    +1 for this - well expressed Masahiro Wakame (@vvakame)

  4. basarat commented on Nov 26, 2014

    @basarat
    Contributor

    👍

  5. Bartvds commented on Nov 27, 2014

    @Bartvds

    👍

  6. Steve-Fenton commented on Nov 27, 2014

    @Steve-Fenton

    👍

  7. vvakame commented on Nov 28, 2014

    @vvakame
    ContributorAuthor

    Ryan Cavanaugh (@RyanCavanaugh) I was misunderstanding spec...

    var array: number[] | Date;
    if(array instanceof Array) {
        array.length;
    }
    

    I think this code evaluate to following pseudo code.

    var array: number[] | Date;
    if ( array instanceof Array ) {
        ( () => {
            // typeof Array.prototype are Array<any>.
            var _array: typeof Array.prototype = <number[]>array;
            ( () => {
                var array = _array;
                array.length;
            })();
        })();
    }
    

    but actual

    var array: number[] | Date;
    if ( array instanceof Array ) {
        ( () => {
            // https://github.com/Microsoft/TypeScript/blob/05fd2e9fb3db8c3af0248a9d16ca198944c91a7e/src/compiler/checker.ts#L4488
            // is this evaluate to {}[] ?
            // type mismatch! can't narrow types.
            var _array: getTypeOfSymbol( typeof Array.prototype ) = <number[]>array;
            ( () => {
                var array = _array;
                array.length;
            })();
        })();
    }
    

    I think this issue is not related to my proposal directly. but I feel "type guards with generics" is very important feature.


    does we need contextual type guards?

    class A<T> {
        data: T;
    }
    
    class AA<T1, T2> extends A<T1> {
        data1: T1;
        data2: T2;
    }
    
    var objA: AA<Date, RegExp> | Date;
    if(objA instanceof AA) {
        // I wish this case narrows to AA<Date, RegExp>. type annotation of objA contains of it.
        objA.data1.getTime();
    }
    if(objA instanceof A) {
        // I wish this case narrows to A<Date>. type parameter picked by AA<Date, RegExp>. it contains type annotation of objA.
        objA.data.getTime();
    }
    
    class AB extends A<number> {
        data1: string;
    }
    
    var objB: AB | Date;
    if(objB instanceof AB) {
        // narrows to AB.
        objB.data1.charAt(0);
    }
    if(objB instanceof A) {
        // I wish this case narrows to A<number>. type parameter picked by AB.
        objB.data.toFixed(2);
    }
    if(objB instanceof AA) {
        // I wish this case narrows to AA<number, {}>. (objB does not have AA types. smells of bug...)
    }
    
    var objC: AA<Date, RegExp> | AA<String, RegExp> | Date;
    if(objC instanceof AA) {
        // narrows to AA<Date, RegExp> | AA<String, RegExp>;
        objC.data1;
        // I wish this case data1ObjC infered type Date | String; 
        var data1ObjC = objC.data1; 
    }
    

    off topic.

    I'm not using VisualStudio.
    I want to see type of variable like this.
    playground_-_welcome_to_typescript

    Is there any way use playgroud of master/HEAD in local environment?


    off topic 2.

    if Property_0_does_not_exist_on_type_1 occured after type narrowing check, I want get warning message ("{0} is not narrowed to {1}.") with it.

  8. danquirk commented on Dec 1, 2014

    @danquirk
    Member

    Unfortunately there's no easy way to get a playground with the latest bits. One hack you can do to check types via the command line is to redeclare the var whose type you are interested in. That should cause an error which will print the relevant types.

  9. vvakame commented on Dec 2, 2014

    @vvakame
    ContributorAuthor

    Dan Quirk (@danquirk) thank you! It very useful information for me.

  10. added this to the milestone on Apr 27, 2015
  11. RyanCavanaugh commented on Apr 27, 2015

    @RyanCavanaugh
    Member

    Approved

  12. modified the milestones: , on May 6, 2015
  13. mhegazy commented on May 6, 2015

    @mhegazy
    Contributor
  14. locked and limited conversation to collaborators on Jun 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixedA PR has been merged for this issueHelp WantedYou can do thisSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions