#type guards are throwing me for a loop

20 messages · Page 1 of 1 (latest)

honest talon
#

so normally, type guards work fine, they let you filter a type or exclude a type
but in my case, it seems ts believes that my function always returns true (so it does no narrowing). if i invert it, it also believes that i have dead code. what's going on?

burnt moonBOT
#

@honest talon Here's a shortened URL of your playground link! You can remove the full link from your message.

ktibow#0

Preview:```ts
type GetHandler<U> = () => Promise<U>
type PostHandler<T, U> = (data: T) => Promise<U>

function isPostHandler(
fn: any
): fn is PostHandler<unknown, unknown> {
return false // placeholder, ts doesn't care about contents
}

const test:
| GetHandler<unknown>
| PostHandler<unknown, unknown> = async () =>
"placeholder"
...```

short urchin
#

@honest talon The problem here is that your types are structurally compatible with each other - PostHandler is assignable to GetHandler

honest talon
#

does ts just not care about the arguments of function types?

short urchin
#

It does, but a function that omits an argument is assignable to one that requires it - functions are allowed to ignore arguments that they're provided.

#

Here's a non-function version of the same behavior:

burnt moonBOT
#
class Animal { name!: string; }
class Cat extends Animal {}

declare function isCat(animal: Animal): animal is Cat;

declare const animal: Animal

if (isCat(animal)) {
  animal
// ^? - const animal: Animal
} else {
  animal
// ^? - const animal: never
}
short urchin
#

Since Cat is structurally identical to Animal, "narrowing" to it doesn't really work (types in TS are structural and this includes custom type-guards)

honest talon
#

so what should i do instead? just use as when needed?

short urchin
#

TBH, type-guards on functions is kind of an odd pattern - you can't really tell at runtime what a function is

#

Except checking .length, I guess, but that's a bit niche

honest talon
#

the code was

if (takesInput(run)) {
  if (request.method != "POST") {
    return new Response("Method not allowed", { status: 405 });
  }
  return Response.json(await run(await request.json()));
} else {
  if (request.method != "GET") {
    return new Response("Method not allowed", { status: 405 });
  }
  return Response.json(await run());
}
short urchin
#

Hmm, I guess I'd be tempted by something more explicit like:

type Handler<In, Out> = 
  | { kind: "post", handler: (input: In) => Promise<Out> }
  | {kind: "get", handler: () => Promise<Out> }
#

Or just have (input?: In) at the type level and leave the extra checking about whether "GET" methods are allowed to expect arguments as a runtime thing

#

Though I guess the simple thing is that the reverse check works - since GetHandler isn't assignable to PostHandler, you can have a type-guard that checks fn is GetHandler and it'll narrow

honest talon
#

i've got something else that works by now

#

!resolve