TypeScript: Regression causes Function top-type to be callable with no arguments

Bug Report

🔎 Search Terms

function top type callable spread never regression

🕗 Version & Regression Information

  • This changed between versions 3.8.3 and 3.9.7

⏯ Playground Link

Playground link with relevant code

đŸ’» Code

declare let foo: (...args: never) => void;
foo = (x: string) => {};
foo();  // no error

🙁 Actual behavior

foo() was permitted.

🙂 Expected behavior

foo() should not be permitted because it is unsafe. (...args: never) => unknown behaves as the top-type for all functions; all function types are assignable to it. So calling it will lead to unsafe behavior by functions expecting 1 or more arguments.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 3
  • Comments: 18 (15 by maintainers)

Most upvoted comments

So, just running into this again, presumably declare const f: (...args: never[]) => unknown should allow f() because [] is assignable to never[], but declare const g: (...args: never) => unknown should not allow g() because [] is not assignable to never. And therefore (...args: never) => unknown is a top type for functions but (...args: never[]) => unknown shouldn’t be.

Right now both types are callable with no arguments and both of these are considered top types for functions. Which of these can we fix without breaking everyone?

We shouldn’t even let you write declare let foo: (...args: never) => void;. It doesn’t make sense.

Are you saying that the type (...args: never) => void doesn’t make sense and should be removed? It seems perfectly logical as a top function type; the type of ...args is, in a sense, the intersection of all possible argument types, which is never. You can’t call a function of type (...args: never) => void, because you don’t know what arguments it takes, but you know that it is something that could be theoretically called, you just don’t know what it could be called with. It’s like {} being the top type of property-bearing things; you can’t directly access properties on it, because you don’t know what properties it might have, but you know that it has properties that could theoretically be accessed.

The second assignment isn’t unsound per our definition of rest args being assumed to have sufficient arity (or more accurately, it’s unsound in the way that all rest args / finite parameter lists are unsound)

Ah, good point.

Would it break the universe if we stopped (...args: never[]) => unknown from being considered a top type? No function that requires an argument should be assignable to it. (e.g., const f: (...args: never[])=>unknown = (x: string) => x.toUpperCase(); f(); compiles and explodes at runtime.) I’m guessing it’s been used as a top type in various real-worldy places and these would be broken if the change were made. But I’d at least like to see an official “we know this is unsound but it’s too late to touch it now” comment somewhere I can refer people to.

Upon further reflection, I think you’re right about (args: never)

So can we confirm that this is a bug and not just “needs investigation” anymore, given the discussion in #35438 ?

declare let foo: (...args: never[]) => void;
foo = (x: string) => {};

makes sense, fundementally, because [string] is assignable to never[]

But [string] isn’t assignable to never[]. Seems that that there is another case of unsoundness with (...args: never[]) => unknown.

let x: (...args: never[]) => unknown = () => {}
const y: (...args: ["one arg"]) => unknown = (x) => alert(x.charCodeAt(0))

x = y;

try {
  x() // throws
} catch(e) {
  alert(e)
}

I think it is just broken that (...args: never) => unknown is callable.