Search code examples
typescriptfunctionvoidany

"Function expression, which lacks return-type annotation, implicitly has an 'any' return type" when adding void operation


I am having a strange issue on TypeScript. I recently learnt about void ... operator because I need to apply it so eslint wouldn't report no-floating-promises. However this particular snippet somehow caused an issue that I cannot reproduce on TypeScript playground:

class A {

  async a() {}

  async onTickAsync(repeat: boolean) {
    try {
      await this.a();
    } catch(e) {
      console.error(e);
    } finally {
      if (repeat) {
        window.setTimeout(() => void this.onTickAsync(true), 200);
      }
    }
  }

}

VS Code would report this error:

enter image description here

TS7011: Function expression, which lacks return-type annotation, implicitly has an 'any' return type.

However, the issue is not reproducible on TS Playground. Both VS Code and Playground is using TypeScript 4.5.4. Here's my tsconfig.json:

{
    "compileOnSave": true,
    "compilerOptions": {
        "noImplicitAny": true,
        "noEmitOnError": true,
        "sourceMap": true,
        "target": "ESNext",
        "module": "ESNext"
    },
    "exclude": [
        "node_modules"
    ]
}

I understand it can be fixed by adding : void return type or removing void operation or removing noImplicitAny:

window.setTimeout((): void => void this.onTickAsync(true), 200);

I want to ask: What causes the error? Why is it happening only on my IDE/local and not the playground?


To be sure it's not just due to VS Code, I ran tsc --version and tsc on a separate Terminal as well:

enter image description here

tsc --showConfig output:

PS C:\Users\lukev\Downloads\Temp> tsc --showConfig
{
    "compilerOptions": {
        "noImplicitAny": true,
        "noEmitOnError": true,
        "sourceMap": true,
        "target": "esnext",
        "module": "esnext"
    },
    "files": [
        "./test.ts"
    ],
    "exclude": [
        "node_modules"
    ],
    "compileOnSave": true
}

It's also interesting that it doesn't happen to other function. This, for example, does not produce any error. It seems to be something with window.setTimeout. I found out there is something different between Function type and () => void for example):

class A {

  doSomething1(_: Function) { }
  doSomething2(_: () => any) { }
  doSomething3(_: () => void) { }

  async a() { }

  async onTickAsync(repeat: boolean) {
    // Only this one produces error
    this.doSomething1(() => void this.onTickAsync(true));
    this.doSomething2(() => void this.onTickAsync(true));
    this.doSomething3(() => void this.onTickAsync(true));
  }

}

Solution

  • See microsoft/TypeScript#36651 for an authoritative answer.

    Your issue is that you have enabled the --noImplicitAny compiler option but you have not enabled the --strictNullChecks compiler option. You can set these options in the TypeScript Playground and reproduce your issue.

    Aside: note that --strictNullChecks is part of the --strict family of compiler features, which is generally recommended as part of a de facto "standard" level of type safety. You're not really asking about which compiler options you should use, but do note that if you use an uncommon set of compiler options you are more likely to run into compiler behavior that is not well known to the general TypeScript community. Okay, enough about that.

    So we know how to reproduce, but haven't explicitly answered why there's an error here. Let's do that now. With --strictNullChecks enabled, the void opterator produces a value of the undefined type. But with --strictNullChecks disabled, there isn't really a distinct undefined type, and the void operator produces a value of the any type. And unless you explicitly annotate that a type is any, you'll get an error under --noImplicitAny:

    // with --strictNullChecks disabled
    
    () => void 0; // error!
    // Function expression, which lacks return-type annotation, 
    // implicitly has an 'any' return type.
    
    (): undefined => void 0; // okay
    //^^^^^^^^^^^ <-- arrow function return type annotation
    

    As you saw, you can also get rid of the error if the return type of the void operator is given a contextual type:

    function foo(cb: () => any) { }
    foo(() => void 0); // okay, void 0 is contextually typed as any
    

    And note that the Function interface is a bit strange and doesn't have a true call signature, see microsoft/TypeScript#20007, so it can't provide a contextual type:

    function bar(cb: Function) { }
    bar(() => void 0); // implicit any error!
    

    Playground link to code