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

Generic type that conditionally returns never can result in a never acting like any when used #50090

Closed
itaylor opened this issue Jul 29, 2022 · 5 comments

Comments

@itaylor
Copy link

itaylor commented Jul 29, 2022

Bug Report

🔎 Search Terms

generics, as keyword, type assertion, never

🕗 Version & Regression Information

I see the same behavior in versions 3.3 through 4.8.0 beta in the TS Playground

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about the never type being used after being returned_____

⏯ Playground Link

Playground link

💻 Code

type SometimesNever<T> = 
  T extends string ? string
    : T extends number ? never
    : never;

function sometimesNeverFn<T>(something: T): SometimesNever<T> {
  return something as SometimesNever<T>;
}

const ok = sometimesNeverFn('something');
doSomething(ok); // expected/actual error: Argument of type 'string' is not assignable to parameter of type 'Date'.(2345)

const wtf = sometimesNeverFn(10);
doSomething(wtf);  // expected: This should error, but doesn't.  `wtf` is type `never` (visible in playground) but we can pass it to a function that expects a Date.

function doSomething(s: Date) {
  s.getDate();
}

🙁 Actual behavior

When a generic type that might be never is returned from a function by using a type assertion in the return, and the type resolves as never, the type is still usable to be passed as an argument to functions, almost as if it was an any.

🙂 Expected behavior

If the compiler is able to resolve a type as never, then the variable with that type should cause an error when passed as a function argument.

I had this occur on a vastly more complex set of generic types and functions that could sometimes be never (and correctly so, as the function did runtime checks and would throw if passed certain types), and it was very unintuitive that the type could be resolved as never but still returned and subsequently passed to another function without an error.

@MartinJohns
Copy link
Contributor

MartinJohns commented Jul 29, 2022

That's working as intended. Nothing is assignable to never, but never is assignable to anything.

See also #49862 (comment) and #28982 for more information.

@itaylor
Copy link
Author

itaylor commented Jul 29, 2022

Yeah, can see that even this works.

function alwaysNever(something: string | number): never {
  throw Error('err');
}

const ok = alwaysNever('something');
doSomething(ok);

const wtf = alwaysNever(10);
doSomething(wtf);  

function doSomething(s: Date) {
  s.getDate();
}

@itaylor
Copy link
Author

itaylor commented Jul 29, 2022

Thanks for the explanation @MartinJohns. I'll close the ticket. It is very unintuitive, but I think I understand why it's happening.

@itaylor itaylor closed this as completed Jul 29, 2022
@itaylor
Copy link
Author

itaylor commented Jul 29, 2022

I think what I'm really wishing for here is something like what is proposed in #23689, a type that is neither assignable to anything nor assignable from anything.

@fatcerberus
Copy link

fatcerberus commented Jul 29, 2022

Yep, #23689 (invalid type) is most likely what you're really looking for.

It is very unintuitive, but I think I understand why it's happening.

Yeah, so never is the bottom type which represents the "result" of infinite loops or exceptions, so you (and the compiler) are free to pretend it's whatever type you want. This is the type-theoretically correct behavior, see #49862 (comment)

tl;dr: If doSomething(neverVal) is reachable, your program is already ill-typed by definition. alwaysNever() can't legally return normally because it's impossible to select an element from an empty set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants