Typescript: User defined type guard function and type any

Created on 4 Dec 2015  ·  9Comments  ·  Source: microsoft/TypeScript

Hi all!

interface A {
    prop: string
}

function isA(arg: any): arg is A {
    return "prop" in arg;
}

function handle(arg: any) {
    if (isA(arg)) {
        let b = arg.nonExistsProp; // no compile error here
    }
}

Why there is no compile error while acessing nonExistsProp? Is it by design and guard function only works with union types? Can't find some sort of specs, please, help me.

Question

Most helpful comment

@RyanCavanaugh what bothers me is that the design team is up for encouraging bad habits, this is wrong, please stop, you cannot follow the flow even if everyone flows with it, you should make what's right, not what's everyone is accustomed to be doing for their lives, i mean an average member of javascript community doesn't have a PhD in computer science, why the hell we choose to follow his bad habits?

type guard should narrow things, this is what they do, narrowing any makes perfect sense

All 9 comments

Type guards do not narrow any. The reasoning for this is that we want type guards to allow you to do _more_, not _less_. any can already do everything, so it doesn't "help" you in that regard.

More specifically, we actually did try it the other way. The initial version of type guards _did_ narrow on any; what we found is that a lot people had code like this:

// By contract, this function accepts only a Dog or a House
function fn(x: any) {
  // For whatever reason, user is checking against a base class rather
  // than the most-specific class. Happens a lot with e.g. HTMLElement
  if (x instanceof Animal) {
    x.woof(); // Disallowed if x: Animal
  } else {
    // handle House case
  }
}

At the expense of being pedantic, this isn't entirely true. instanceof and user-defined type guards do not perform narrowing. typeof type guards _do_ because we know _exactly_ what type a value has.

let x: any;

if (typeof x === "number") {
    // 'x' has type 'number' here.
    let y = x * x;
}

@RyanCavanaugh what bothers me is that the design team is up for encouraging bad habits, this is wrong, please stop, you cannot follow the flow even if everyone flows with it, you should make what's right, not what's everyone is accustomed to be doing for their lives, i mean an average member of javascript community doesn't have a PhD in computer science, why the hell we choose to follow his bad habits?

type guard should narrow things, this is what they do, narrowing any makes perfect sense

+1 on changing the behavior to narrowing the type.

With respect to prior user behavior, I don't think it's something to be encouraged, but if it is widespread, it's because instanceof existed prior to typeguards being introduced. For user-defined typeguards however, that's not the case. If I define a function with the signature (arg: any) => arg is A, there's zero ambiguity that the user intends for any type to be narrowed to A.

Alternatively, maybe there could be some sort of syntax where the user can explicitly indicate intent to narrow? E.g. isA(arg: any) => arg isOnly A?

I'm serious about the number of breakages we found in real-world code. It was very large, and feedback from people trying early builds said they didn't want this.

It's so far been basically impossible to get a type error on a value of type any and there would need to be a stronger justification other than "You should be stricter on people" when the entire point of the any type is to be as unstrict as possible.

On the one hand, listen to users, and on the other hand, listen to users...

Don't see a contradiction. Let any be the most unrestricted thing in the world. No problem. But when someone does if (anything is Potato) { eatSteak(anything); } they expect a compiler error, any won't give it to them, so they don't want any, they want a shaped value. So the strictness is welcome here, it's a good thing, why would anyone be pissed by it.

What you've written there is runtime-invalid code. The point is that there's lots of runtime-_valid_ code that also issues a type error if we narrow any

Let's not use word strictness or narrowing for a second. Type guard is a predicate that asserts at runtime that a given value is of certain type. Ultimately one can put anything in it and it would say yes or no. Once it said yes you get a guarantee (up to the implementation of the type guard) that a value is of that type. Am I right so far? If so, then your example with the woof method must break. We asserted that a value is of type Animal. Animals don't woof, dogs do. It's not a runtime valid code, because if I put a cat in that function it will crash regardless of what the author thought the contract is. There is no such thing as a contract in JavaScript, there are only our best assumptions and hopefully a test that verifies them. So I am not sure why we are into giving the developers an illusion that they know what they are doing. I see your point that there are a lot of naive people who already written tons of crappy code, well this is TypeScript for goodness sake, time to grow up.

I am not trying to convince you. Just ranting. I get it that the design goal is to make a mainstream language that fits the least competent developer. It is indeed very frustrating that correctness isn't the goal. I get that too. There is already quite a number of features that I wish didn't exist at all, we one more one less doesn't make a difference. Hopefully the niche for a correct/sound counterpart of TypeScript will be filled soon. As soon as we have it we will get a natural partitioning between the skilled developers and beginners.

Was this page helpful?
0 / 5 - 0 ratings