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

Allow a type guard function to also return a boolean literal whenever possible #57821

Closed
6 tasks done
Evertt opened this issue Mar 18, 2024 · 3 comments
Closed
6 tasks done

Comments

@Evertt
Copy link

Evertt commented Mar 18, 2024

πŸ” Search Terms

I've searched for "guard" and "boolean literal", and I did not find what I'm looking for.

βœ… Viability Checklist

⭐ Suggestion

Consider this code:
type Primitive = string | number | bigint | boolean | symbol | null | undefined
type IsPrimitive<T> = T extends Primitive ? true : false

function guardIsPrimitive(thing: unknown): thing is Primitive {
  const primitiveTypes = ["string", "number", "bigint", "boolean", "symbol", "undefined"]

  return thing === null || primitiveTypes.includes(typeof thing)
}

function isPrimitive<T>(thing: T) {
  return guardIsPrimitive(thing) as IsPrimitive<T>
}

// Example usage:
const value: unknown = "hello";
if (guardIsPrimitive(value)) {
  value // value is narrowed to be a `Primitive`
  // ^? const value: Primitive
}

isPrimitive(123) // returns the boolean literal `true`
// ^? function isPrimitive<number>(thing: number): true

Or just open up this playground.

In any case, you see I needed to create two separate functions to get what I wanted. I have tried everything I could and there simply seems to be no way to write a single function that can serve simultaneously as a type guard and return a boolean literal that tells you whether the type you put in was in fact a Primitive type.

πŸ“ƒ Motivating Example

On the one hand, this is probably a small or even non-existent annoyance for most developers. On the other hand, I think these small DX improvements can mean a lot in the long run.

So I would super appreciate if you guys could implement this in some way. And of course I understand that this might not be trivial to implement, since you probably want to make sure that whenever a function returns something like isPrimitive<T> & (thing is Primitive), that whatever isPrimitive<T> is doing returns the exact same boolean values for thing as thing is Primitive does. And it may not be trivial to implement such a type check.

So I would understand if you would say that it's not worth investing the time and energy needed to implement this feature considering how small of a DX improvement it brings. I thought I'd still just ask though.

Alternative I just thought of

This would be a breaking change though, so I'm afraid this one might be even less viable to get implemented. But an alternative would be to change the way thing is Type works such that it in fact returns a boolean literal whenever the compiler can already figure out the result of that function at compile time. As in guardIsPrimitive(123) would return thing is Primitive & true. That way it should both be usable for type-narrowing in if-statements and for evaluating the validity of some code while writing it (before needing to run it).

πŸ’» Use Cases

  1. What do you want to use this for?
  2. What shortcomings exist with current approaches?
  3. What workarounds are you using in the meantime?

I think I've answered all of these questions above, didn't I?

@Evertt Evertt closed this as completed Mar 18, 2024
@Evertt Evertt reopened this Mar 18, 2024
@fatcerberus
Copy link

I think I've answered all of these questions above, didn't I?

You didn't answer the most important one: what is the use case? AFAICT, in any case where you can know at compile time what the result of a type guard is going to be, then there's no reason to induce runtime overhead by calling it.

@whzx5byb
Copy link

Does Function Overloading work for you?

type Primitive = string | number | bigint | boolean | symbol | null | undefined
declare function isPrimitive<T extends Primitive>(thing: T): true;
declare function isPrimitive(thing: unknown): thing is Primitive;

// Example usage:
const value: unknown = "hello";
if (isPrimitive(value)) {
  value // value is narrowed to be a `Primitive`
  // ^? const value: Primitive
}

const r = isPrimitive(123) // returns the boolean literal `true`
//    ^? const r: true

@Evertt
Copy link
Author

Evertt commented Mar 18, 2024

@whzx5byb wow, that is so much closer than I have been able to get. I'm inclined to say that that is good enough to be able to close this issue.

I have been trying to see if I can create a second overload to cover the false case, but I'm not managing that one just yet. Although that's now a little less important, because I can just infer myself that if the function's return-type is boolean instead of true, that I should interpret that as false.

But, if you happen to be willing to try to help me achieve the false case as well, then I'd be super grateful!

Edit

@whzx5byb never mind! I figured it out! Here it is!

@Evertt Evertt closed this as completed Mar 18, 2024
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