Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow assigning void to unknown #50752

Closed
5 tasks done
stevenwdv opened this issue Sep 13, 2022 · 10 comments
Closed
5 tasks done

Disallow assigning void to unknown #50752

stevenwdv opened this issue Sep 13, 2022 · 10 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@stevenwdv
Copy link

stevenwdv commented Sep 13, 2022

Suggestion

πŸ” Search Terms

  • assign void to unknown
  • disable assign void unknown
  • disallow assign void unknown

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
    I think this would be acceptable, but if not, a compiler flag could be added so it does not break existing code.
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Disallow the assignment of void expressions to unknown type. void means a value is not supposed to be used, and assigning it to an unknown variable/parameter contradicts this.

This is like #45750 but with unknown instead of any. I think this proposal may have a higher chance to be accepted since any is sort-of meant to accept any value, however absurd, and e.g. some people may define callback functions as () => any, which would then have the unintended effect of blocking passing () => void.

πŸ“ƒ Motivating Example

function print(obj: unknown) {
    console.log(obj);
}
function extendArray<T>(array: T[], append: Iterable<T>) {
    array.push(...append);
}
const names = ['jan', 'pier'];
print(name); // oops, typo, but allowed because typeof window.name extends void
print(names.forEach(n => n.toUpperCase())); // oops, used forEach instead of map
print(extendArray(names, ['tjores', 'corneel'])); // oops, extendArray does not return the array
// Imagine using a headless browser API or something
interface DummyWebPage {
    executeScript(script: string): void;
}
interface ICrawler {
    execAndGetData(page: DummyWebPage): unknown;
}
// A crawler implementation that does not return data on its own
// but just closes cookie prompts
const cookiePromptCloser: ICrawler = {
    // oops, we return void instead of just e.g. null/undefined
    // We have no idea what's actually returned from executeScript
    execAndGetData: page => page.executeScript('cookiePrompt.close()'),
};
// call cookiePromptCloser.execAndGetData...

Also see #45750, but the DOM lib currently uses any a lot, so it won't have as much effect there yet.

πŸ’» Use Cases

Prevent usage of a value which was not meant to be used.
E.g. prevent bugs with passing a void result to logging or serializing functions.

I think this would also help mitigate #18433.

@fatcerberus
Copy link

fatcerberus commented Sep 13, 2022

See also #42709. Possibly a duplicate but maybe not; the void rabbit hole goes pretty deep.

@stevenwdv
Copy link
Author

@fatcerberus I don't think #42709 suggests this, does it? In any case, it seems to be more ambitious and I'm not sure if I agree with all suggestions made.

@fatcerberus
Copy link

fatcerberus commented Sep 13, 2022

Oh definitely, #42709 is way more ambitious. void is a really deep rabbit hole (...void-hole?). The relevant part though is this:

The handbook hints at the original use-case for void, which is that it was used to model functions which don’t return, i.e. functions without a return statement. Although the runtime behavior of these functions is to return undefined, maintainers argued for modeling these functions as returning void instead at the type level because implicit return values are seldom used, and if they are used this usually indicates some kind of programmer error.

Which argues to your point, that void shouldn't be assignable to anything. You can't even treat it as undefined because () => string is assignable to () => void.

@stevenwdv
Copy link
Author

stevenwdv commented Sep 13, 2022

@fatcerberus Ah, I see your point. I thought that this was the official/intended purpose of void, due to some StackOverflow answer I once read, but I see that the official docs do not explicitly mention this. Anyway, this is how I think about void.

@stevenwdv
Copy link
Author

Wait, the StackOverflow answer I mentioned is by Ryan, who is the dev lead, so I guess that does make it the official purpose.

@fatcerberus
Copy link

Yep, void is essentially the /dev/null type - there's a value there (i.e. it's not never because that would imply unreachable code), but you're not supposed to access it because the type de facto means "value that will always be discarded".

To be 100% clear, I agree with your suggestion - void should not be assignable to anything, not even unknown.

but I see that the official docs do not explicitly mention this

The official docs could definitely use some fleshing out, IMO.

@MartinJohns
Copy link
Contributor

MartinJohns commented Sep 13, 2022

Plenty of information got lost as part of v2 of the handbook as well. The old handbook writes about void:

void is a little like the opposite of any: the absence of having any type at all. You may commonly see this as the return type of functions that do not return a value:

Emphasis mine. Can be misunderstood as well, but I always understood "The absence of any type" as "do not attempt to use this value, it could be anything".

@andrewbranch andrewbranch added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Sep 16, 2022
@MathiasKandelborg
Copy link

This seems like a good idea, any reasons why this suggestion could cause problems?

Decreasing developer errors is always welcome in my books.

@RyanCavanaugh
Copy link
Member

This is actually not doable; many things would break.

If void isn't assignable to unknown, then () => void can't be assignable to () => unknown, and if that's not assignable, then things like this need to change behavior

// Implicit constraint of F is 'unknown'
type ReturnType<F> = F extends (() => infer U) ? U : "no";

// Correct type of S is "no", instead of void (today)
type S = ReturnType<() => void>;

So that means that we'd have to change the implicit constraint of all generic types to unknown | void, which would have endless follow-on problems, and just breaks the model of "unknown is the top type".

The example in the OP is, to me, completely uncompelling. unknown means any value; a function returning void is defined to mean "could return any value". No semantic violations have occurred and there are many other mistakes you could make with this function that aren't related to void at all:

print(foo.getLength); // forgot to call the function!

@RyanCavanaugh RyanCavanaugh added Declined The issue was declined as something which matches the TypeScript vision and removed Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Sep 26, 2022
@RyanCavanaugh RyanCavanaugh closed this as not planned Won't fix, can't repro, duplicate, stale Sep 26, 2022
@stevenwdv
Copy link
Author

Ah okay, thanks for the explanation. That's unfortunate, so I guess we don't really have a type that means "anything that you're meant to look at", only "anything and maybe you're not supposed to look at this". In my head void was indeed sort-of a supertype of unknown.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants