TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

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

Open webstrand opened this issue 2 years ago • 15 comments

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.

webstrand avatar Apr 25 '22 15:04 webstrand

:wave: Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of the repro in the issue body running against the nightly TypeScript.


Issue body code block by @webstrand

:+1: Compiled

Historical Information
Version Reproduction Outputs
4.2.2, 4.3.2, 4.4.2, 4.5.2, 4.6.2

:+1: Compiled

typescript-bot avatar Apr 25 '22 20:04 typescript-bot

@typescript-bot bisect good v3.8.3 bad v3.9.7

andrewbranch avatar Apr 25 '22 20:04 andrewbranch

I think this aliasing unsoundness is actually unrelated to whether (...args: never) => void is callable. Consider (...args: never[]) => void or even (...args: string[]) => void. The assignment to foo in your example would still work, and yet these types are obviously callable with no arguments.

andrewbranch avatar Apr 25 '22 20:04 andrewbranch

The change between v3.8.3 and v3.9.7 occurred at 0e15b9f2457b66ee9c14e42292ea5e5d9d78258e.

typescript-bot avatar Apr 25 '22 20:04 typescript-bot

I've always considered this to be the distinction between (...args: never) => unknown and (...args: never[]) => unknown. The latter has to be callable with no arguments because never[] is assignable from []. Otherwise, without (...args: never) => unknown, there is no top-type for all functions.

webstrand avatar Apr 25 '22 20:04 webstrand

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

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

webstrand avatar Apr 25 '22 20:04 webstrand

Otherwise, without (...args: never) => unknown, there is no top-type for all functions.

Well, (...args: never[]) => unknown is still a function top type in that every other function type is assignable to it (the two formulations in question are cross-assignable). But it does feel like you ought to be able to express a function type constraint that is itself not callable because you have no idea what to pass it 🤔

andrewbranch avatar Apr 25 '22 20:04 andrewbranch

For real-world use, this most commonly shows up in my code like

function foo<T extends (...args: never) => unknown>(fn: T) {
    fn(); // no error
}

or

const registry = new Map<(...args: never) => unknown, unknown[]>();
for(const [fn, data] of registry) {
    fn(); // no error
}

webstrand avatar Apr 25 '22 20:04 webstrand

Well, (...args: never[]) => unknown is still a function top type in that every other function type is assignable to it

Actually I found a counterexample from AnyhowStep by reading around https://github.com/microsoft/TypeScript/issues/48840#issuecomment-1109002426: https://www.typescriptlang.org/play?#code/PQKhCgAIUhlBDAZgUwE6QG5oM4EsD2AdpPopAAYBiAroQMYAuBh5kA7gBa50eTYf5qAGwAmkQvgaQARskh14QochEA6KDACiWVAE9IiWo2Z8BwsbMjxseAOaF405ZAb4XXbABoZ1KQw+mgqLikjJy1nYOTnKukMg6+ob0TETq0MDgyAAeAA74qH66OXIAgoS6NMkmALyQABSqjfCottgAXOLxaADaALoAlJDVAHyY+LgiANzg4EnGRAb4+AA8ACpxWQzIhCLYVuV9w3WDAN5QkBd0RNhSiIQAjB0NjajINx2rgyNjE0P1jaoAPpfUYnAC+0wul2ut0IACYOmUKkYUsRand7pBgMA4qhUPlwGCgA

andrewbranch avatar Apr 25 '22 20:04 andrewbranch

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.

jack-williams avatar Apr 26 '22 08:04 jack-williams

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

jcalz avatar Jun 01 '22 15:06 jcalz

It seems there are two different unsoundnesses being discussed here:

declare let foo: (...args: never) => void;
foo(); // should error (`[]` isn't assignable to `never`), but does not
declare let foo: (...args: never[]) => void;
foo = (x: string) => {}; // should error (`never[]` isn't assignable to `[string]`), but does not

Both of these unsoundnesses relate to a top function type being callable; however, the correct fix for the former is to make the type (...args: never) => void uncallable but still a top function type, while the correct fix for the latter is to make the type (...args: never[]) => void not a top function type but still callable.

tjjfvi avatar Oct 06 '22 20:10 tjjfvi

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

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)

RyanCavanaugh avatar Oct 06 '22 20:10 RyanCavanaugh

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.

tjjfvi avatar Oct 06 '22 21:10 tjjfvi

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

RyanCavanaugh avatar Oct 07 '22 19:10 RyanCavanaugh

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?

jcalz avatar Dec 30 '22 16:12 jcalz

I've been using and teaching (...args: never) => unknown as the supertype of all callables.

webstrand avatar Jan 07 '23 04:01 webstrand

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.

jcalz avatar Mar 06 '23 14:03 jcalz