skip to Main Content

I have a function defined like this:

export async function removeUser(deleteUserId: Types.ObjectId)

When I mistakenly called this function and pass a Mongoose object’s id parameter, which is a string, it triggered a runtime exception further in the code when I tried to use the .equals() method which exists on an ObjectId but not on a string:

await removeUser(deleteUser.id);

When I corrected this by passing the ObjectId, everything worked fine:

await removeUser(deleteUser._id);

My question is, why didn’t I get a compile time error from Typescript if the argument is specified as an ObjectId, but a string is being passed?

Edit: I see that the .id is defined as any, not as a string. I suspect that will factor into the answer?

3

Answers


  1. Yep, if we can somehow check for any, then we can make sure that values of type any cannot be passed:

    type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
    
    export async function removeUser<Id extends Types.ObjectId>(deleteUserId: IfAny<Id, never, Id>) {
    

    So if we pass in any, the type of deleteUserId will turn into never:

    await removeUser(deleteUser.id);  // error, type 'any' is not assignable to type 'never'
    await removeUser(deleteUser._id); // ok
    

    Playground

    Login or Signup to reply.
  2. I see that the .id is defined as any, not as a string.

    Values of type any allow assignment to any type.

    const n: number =  'string' as any // fine
    

    It’s an escape hatch from the type system. So any is not typesafe and should be avoided at all costs.

    See Documentation:

    The compiler effectively treats any as “please turn off type checking for this thing”. It is similar to putting an @ts-ignore comment around every usage of the variable.

    Login or Signup to reply.
  3. Typescript types exist only in compile time, not in runtime.
    Therefore, Typescript type system is not sound — it cannot guarantee you that if your program compiles, it will not throw any error in runtime.

    Back to your question, if a variable type is any, you can do anything with it, and Typescript will not even blink. You should use any type with an extreme caution.

    For example, if you have a variable obj of type any:

    • You could assign obj to any other variable (except of type never)
    • You could access any fields on obj e.g. obj.a.b.c.d
    • You could pass it to any function, in particular that expecting string

    Code example:

    function onlyAcceptingNumber(a: number) {}
    
    export async function doSomething(obj: any){
      // I can do this:
      const a = obj.a * obj.b;
      // I can also do this:
      console.log(obj.a.b.c.d);
      // and I can call onlyAcceptingNumber with obj
      onlyAcceptingNumber(obj);
    }
    
    // but of course this could fail in runtime:
    doSomething("123");
    doSomething({ name: "John" });
    

    In the example above, the doSomething function would compile, and both calls to it would compile, but of course if we run everything, it would fail in runtime.

    To battle this, you can use e.g. ESLint’s no-explicit-any rule, and in general only use any as a last resort

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search