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

Differing user-defined type guard and 'typeof' type guard behaviour when narrowing 'any' #6015

Open
pimterry opened this issue Dec 9, 2015 · 9 comments
Labels
Effort: Moderate Requires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual". Help Wanted You can do this Suggestion An idea for TypeScript
Milestone

Comments

@pimterry
Copy link
Contributor

pimterry commented Dec 9, 2015

In the below code, using a typeof type guard and an equivalent (I thought) user-defined guard, only one error is produced.

var y: any;

// Built-in type guard
if (typeof y === "string") {
    y.hello = true; // Correct error - 'hello' does not exist on type string
}

// Equivalent user-defined type guard
function f(x: any): x is string {
    return typeof x === "string";
}

if (f(y)) {
    y.hello = true; // No error with user-defined guard
}

Playground demo. Looks like user-defined type guards won't narrow from any, in any circumstances, as far as I can tell.

@pimterry
Copy link
Contributor Author

Any thoughts on this?

Looks clearly incorrect to me, but I can't find a definitive spec on user-defined type guards, which makes it difficult to verify. I'd be very surprised that they're actually intended to differ from built-in type guards in this regard though.

@DanielRosenwasser DanielRosenwasser added the In Discussion Not yet reached consensus label Dec 15, 2015
@DanielRosenwasser DanielRosenwasser changed the title Differing user-defined type guard and built-in type guard behaviour when narrowing 'any' Differing user-defined type guard and 'typeof' type guard behaviour when narrowing 'any' Dec 15, 2015
@DanielRosenwasser
Copy link
Member

The thing is that user-defined type guards are "weaker" than typeof guards given that an implementation might use instanceof in some way. instanceof type guards have to be weaker because narrowing a value's type from any to Object limits what you can do with it. Clearly this gives you a not-so-great experience when you try to work with primitives.

@ahejlsberg and I have discussed this offline and one idea we have is that for any primitive type covered by a typeof type guard (i.e. string, number, boolean, and symbol), we should consider performing the same narrowing as a typeof type guard because that's usually how you were going to implement something like isString.

@DanielRosenwasser DanielRosenwasser added this to the TypeScript 2.0 milestone Dec 15, 2015
@DanielRosenwasser DanielRosenwasser self-assigned this Dec 15, 2015
@DanielRosenwasser DanielRosenwasser added Suggestion An idea for TypeScript Help Wanted You can do this Effort: Moderate Requires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual". labels Dec 15, 2015
@DanielRosenwasser
Copy link
Member

If anyone is considering sending a PR for this, give a heads up here so we don't duplicate work. 😃

@thebanjomatic
Copy link

I've been playing around with the controlFlowTypes branch and it appears as if the same narrowing behavior should probably apply there as well:

For example:

let a: any;
a = "10";
a; // a: any ... expected a: string
a.toFixed(2); // no compile error!

I have been using noImplicitAny in my own code base, but I can imagine some smarter behavior here could only help to find bugs in code which does wind up using any (whether implicitly or explicitly).

@mhegazy mhegazy removed this from the TypeScript 2.0 milestone May 4, 2016
@basarat
Copy link
Contributor

basarat commented May 11, 2016

Came up on stackoverflow : http://stackoverflow.com/a/37153565/390330 🌹

@jstaro
Copy link

jstaro commented Apr 7, 2017

Writing a type guard for something that TS thinks is any is basically what I've always tried to use this feature for, and it fails 100%, making the language feature DOA for me. I just end up casting instead, which makes me feel unclean :(

@RickCarlino
Copy link

RickCarlino commented Apr 8, 2017

@jstaro This does not seem to be a problem with TSC > 2.2 and latest VS Code.

interface User {
  name: string;
}

function isUser(input: any): input is User {
  return (input && input.name);
}

let x = JSON.parse('"any"');

if (isUser(x)) {
  x; // Works fine now.
} else {
  x;
}

@ShuiRuTian
Copy link
Contributor

@DanielRosenwasser this should have been fixed?

@onlynone
Copy link

This appears to be fixed for user defined type guards, but strict equality === appears to still not narrow any types:

var y: any;

// Built-in type guard
if (typeof y === "string") {
    y.hello = true; // Correct error - 'hello' does not exist on type string
}

// Equivalent user-defined type guard
function f(x: any): x is string {
    return typeof x === "string";
}

if (f(y)) {
    y.hello = true; // This is now a correct error with latest TS version: Property 'hello' does not exist on type 'string'.
}

if(y === "hello") {
    y.hello = true; // No error with strict equality guard
}

Is that intentional?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Effort: Moderate Requires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual". Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

10 participants