#type guards are throwing me for a loop
20 messages · Page 1 of 1 (latest)
@honest talon Here's a shortened URL of your playground link! You can remove the full link from your message.
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"
...```
@honest talon The problem here is that your types are structurally compatible with each other - PostHandler is assignable to GetHandler
why's that? any issues/doc links?
does ts just not care about the arguments of function types?
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:
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
}
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)
so what should i do instead? just use as when needed?
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
yeah that was my use
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());
}
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
i was a bit too late to see this, but yeah this sounds like the correct answer
i've got something else that works by now
!resolve