Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type Guard syntax for array #1587

Closed
basarat opened this issue Jan 4, 2015 · 5 comments · Fixed by #1657
Closed

Type Guard syntax for array #1587

basarat opened this issue Jan 4, 2015 · 5 comments · Fixed by #1657
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@basarat
Copy link
Contributor

basarat commented Jan 4, 2015

Perhaps a bug, the following does not work although I think its the right syntax:

class Message {
    value: string;
}

function saySize(message: Message | Message[]) {
    if (message instanceof Array) {
        return message.length; // test.ts(7,24): error TS2339: Property 'length' does not exist on type 'Message | Message[]'.
    }
}

However, the following does work.

class Message {
}

function saySize(message: Message | Message[]) {
    if (message instanceof Array) {
        return message.length; // Okay
    }
}

Note to team: Feel free to edit this query to increase its life. original report: #805 (comment).

@ahejlsberg
Copy link
Member

A type guard x instanceof C narrows the type of x to C only if C is a subtype of the type of x (i.e. only if C is a more specific type than the actual type of x). As long as x is not of a union type that makes sense, as nothing would be gained by making the type of x less specific. However, it does appear we need to be a bit smarter with union types. Specifically, if x is of a union type U, I think we should remove from U all the types that are not subtypes of C. So, in the example

class Message {
    value: string;
}

function saySize(message: Message | Message[]) {
    if (message instanceof Array) {
        return message.length;  // message of type Message[] here
    }
}

we would want the type guard to remove the type Message from the union type.

Now, the reason you don't get an error when the Message class is empty is that Message[] is now a subtype of Message (because it is the empty type), so the union type simply disappears.

@ahejlsberg ahejlsberg added the Bug A bug in TypeScript label Jan 4, 2015
@jeffreymorlan
Copy link
Contributor

As is, it's legitimately possible for an object to both be an Array and implement the Message interface, so that guard would be unsafe:

interface ArrayMessage extends Array<string>, Message {}
var am = <ArrayMessage>['foo'];
am.value = 'bar';
// "am instanceof Array" is true, but am is a Message and *not* a Message[]
saySize(am); // will do the wrong thing

To make this kind of type guard safe, there would need to be some way to "seal" the Message type so it can't be extended, or perhaps make it nominal so that an object that merely has a value: string field isn't considered a Message.

@ahejlsberg
Copy link
Member

Right, since interfaces are structural we can technically never trust an instanceof check to exclude an interface (because the run-time shape of the instance being checked might actually satisfy the interface). But, by that line of reasoning we basically couldn't do any kind of reasoning related to instanceof in a type guard.

@danquirk
Copy link
Member

danquirk commented Jan 5, 2015

@jeffreymorlan

To make this kind of type guard safe, there would need to be some way to "seal" the Message type so it can't be extended, or perhaps make it nominal so that an object that merely has a value: string field isn't considered a Message.

While this is true, people write type guards in JavaScript all the time which are equally susceptible to this sort of break. We should do what we can to help them since the pattern isn't going away.

@Griffork
Copy link

Griffork commented Jan 6, 2015

Surely you can just assume, that in

interface ArrayMessage extends Array<string>, Message {}
function foo (in: Messaage | Message[]) {
    if (message instanceof Array) {
        //message is of type Message and Message[] here, since both are valid?
    }
}

As for interfaces, I vote you only expect what's defined on the interface:
e.g.

interface mything {
    a: string
}
interface myotherthing {
    b: number
}

function foo(athing: mything|myotherthing) {
    if (typeof athing.a !== "undefined") {
        //Here we typeguard to mything, even though the object can have both an a and a b, we've specified that we're interested in the a at the moment.
        //possibly you could (for interfaces) allow checks of other variables (e.g. the existance of b) within the type guard, but not accesses unless within a type-guarded statement
        if (typeof athing.b !== "undefined") {
           //Here athing is a mything that has a b of some kind (but is not necessarily a myotherthing)
           console.log(athing.b); //valid.
        }
    }
}

This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants