0

I want Boolean(truthy) to yield a type of true instead of Boolean, and likewise for falsy => false

So I wrote this:

interface BooleanConstructor {
  <T extends false | 0 | '' | null | undefined>(value?: T): false;
  <T extends Record<any, any> | string | number | true>(value?: T): true;
  <T>(value?: T): boolean;
}

Works great, except for any and unknown. Any ideas?

const A = Boolean(42 as any);     // false ??
const B = Boolean(42 as unknown); // boolean
const A2 = Boolean('');           // false
const B2 = Boolean(0);            // false
const C = Boolean(42);            // true
const D = Boolean('hello');       // true
const E = Boolean(true);          // true
const F = Boolean(false);         // false
const G = Boolean(null);          // false
const H = Boolean(undefined);     // false
const I = Boolean([]);            // true
const J = Boolean({});            // true

ts playground with a local function instead, but same same.

  • 1
    Hmm, this one is weird: ` | any | true>(value?: T): true;` this actually matches the `unknown`. If `| any` is removed then `B` is `boolean`. However, `A` picks whatever the first overload is and uses its return value. If you swap the first two, then `A` is inferred as `true`. https://tsplay.dev/m043Rm – VLAZ Jul 11 '22 at 14:35
  • whoops, the `| any` was me testing. Removed and fixed the playground link. I guess the only problem is `any` which resolves to `false` –  Jul 11 '22 at 14:41

1 Answers1

0

Boolean(42 as any) can match either overload (because any matches anything), so it will resolve whichever comes first. If you swap your overload order, you'll get true instead of false here.

For a hack that might work, see this playground. Basically, you need a new overload where T looks for a type that won't match any of your other overloads to go first, something like:

interface BooleanContstructor {
  <T extends { [`@hopefully-this-long-string-will-never-be-used-as-a-property`]: {} }>(value: T): boolean
  // ...

Or, do this.

Connor Low
  • 5,900
  • 3
  • 31
  • 52
  • yeah, so... any ideas on how to fix it so that `any` resolves to `boolean`? It doesn't seem possible to match the `any` type specifically –  Jul 11 '22 at 14:43
  • While I see the usefulness of what you are trying to accomplish, the way `any` works makes this impossible. – Connor Low Jul 11 '22 at 14:49
  • Not buying that, lol –  Jul 11 '22 at 14:51
  • Okay, after a bit of investigation, I acknowledge it is not impossible, but you might not like the solution: if you create a specific type (i.e. not `any` or `unknown`) that doesn't match any of your other cases, you can get this to work. I'll attach a playground to my answer. – Connor Low Jul 11 '22 at 14:57
  • 1
    @Voice see https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360 – jcalz Jul 11 '22 at 15:00
  • haha, that's genius, just match an impossible type. –  Jul 11 '22 at 15:04
  • FYI jcalz's linked answer to a similar scenario looks less gross. – Connor Low Jul 11 '22 at 15:05
  • yeah, neither actually quite works. `Boolean(undefined)` catches for some reason?? Especially confusing since the `IfAny` type doesn't catch it if it's just the type, but it does in a function signature –  Jul 11 '22 at 15:10
  • [playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAkgZgQQHYgDwBUA0UCa2ByAfFALxQAMUEAHsBEgCYDOUAFAIxQBkU6AlFAD8uKAC4o+ANxQAUDIYQAxgBsAhgCdocAK5JFwAJYB7JFABGRo8oxVa9ZrEQoM2YOu0RsSCADcI6wkJWH1VlD0FxfnEAckVVbQBzAAtgaPklNU0oHT1DE3NLa3RbOkYWOFCmaAAfCiha6Oj6qCRtZWVm3QU4A28GIJCwiAjePnEK5SrJdJUNLV19Y1MLKxsaUocAJSUjdQZUVRRsQ5BiWqY3XoTm1oBbM39mtw8B0PDIsahniGmFWaycot8isiq8hiMogUrBBDtMZIoTBcoAhSFDlKwACwAJigqhYJz40igxKgAHpSdlKtBBIJ4YjgFAAEKokGYnF4qC6ADWSCMAHckISyRSQTCkHSkEiABIswqsLoQHp9IUk4WUyYQGRAA) –  Jul 11 '22 at 15:12
  • FYI this is resolved by making the argument in the function signature that checks for any non-optional: `(value: T)` vs `(value?: T)`. `undefined` seems to act as `any` inside of generics if it's optional, which definitely seems like a TS bug, and I reported it –  Jul 11 '22 at 15:54
  • Not a bug, because making a parameter optional with [`?` implicitly allows `undefined`](https://www.typescriptlang.org/docs/handbook/2/functions.html#optional-parameters:~:text=the%20x%20parameter%20will%20actually%20have%20the%20type%20number%20%7C%20undefined). – Connor Low Jul 11 '22 at 15:59
  • Yes, `undefined`, not `any`. The issue is that `(arg?: T)` passes if you call `fn()`. `undefined` does not extend `42` –  Jul 11 '22 at 16:42