Skip to content

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

New issue

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

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

Already on GitHub? # to your account

Open
pimterry opened this issue Dec 9, 2015 · 10 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?

@d07RiV
Copy link

d07RiV commented Apr 17, 2025

It appears to be "fixed", but I don't think that's the correct behavior? Consider the following example, I should be able to access arbitrary fields on any type. This would make sense if the variable was unknown, but type guards probably shouldn't affect any.

const x: any

if (_.isObject(x)) {
  x.foo // error - why?
}

# for free to join this conversation on GitHub. Already have an account? # 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