#complex overload signatures

117 messages · Page 1 of 1 (latest)

south fiber
#

@delicate jacinth

delicate jacinth
#

FYI i have a meeting in 10 mins, but will take a quick look now

south fiber
#

absolutely no problem

delicate jacinth
#

weird, the playground isn't highlighting the problematic call site for me. is that happening for you too?

#

i only see a red squggly at the function definition

south fiber
#

same

#

but i'm used to getting weird errors by now

#

i'm probably doing something wrong but don't know any better

delicate jacinth
#

i think you're referring to the server2 call. do you expect that call to match the first overload signature? or no?

south fiber
#

nah you can ignore server2 since it's a more complex version of server1

#

i think the issue is actually the overload itself

delicate jacinth
#

okay then i'm getting confused by the error message then. which call is failing? server1 or client1 or both?

delicate jacinth
south fiber
#

yeah i'm trying to figure out what i'm doing wrong, it seems to get screwy when i add undefined

delicate jacinth
#

oh duh sorry, i was primed by the error message you mentioned in #ts-discussion and didn't even read this one. this is a different error

south fiber
#

yeah don't worry, i think it's all related

#

i tweaked something and that one disappeared

#

it's all to do with something in the overloads being off

delicate jacinth
#

so the gist of this one is that the options type you define in each overload must be a subtype of the options type in the implementation signature. ditto for the return types

#

i haven't yet figured out which specific thing is violating that

#

seems like the return types?

south fiber
#

i am getting it back to basics atm

delicate jacinth
#

i think you'll at least need to make server and client optional in the implementation signature

south fiber
#

ok so i have it back at a basic thing that 'works' to go from

#

so here's where it starts getting messy

#

L 14

delicate jacinth
#

sorry but i gotta run now. feel free to keep posting and i'll catch up later

south fiber
#

thanks

#

z.infer<z.ZodObject<ST>>
if i want to combine ST & CT - since i don't want CT to be a default of ZodRawShape, i need to add | undefined afaik

#

this seems to work

#

so, if i just build up until i encounter an error you'll have something to answer

#

now i want to introduce HasKeyPrefix

#

what's the best route forward here

#

anything i attempt seems to not work, so i guess this is the blocker as things stand

#

as far as i can tell the implementation at the bottom of the overloads seems ok here btw?

delicate jacinth
#

i'm back now

#

is the | undefined on CT important to you? it's not the cause of the overload error, just checking whether i can ignore it

south fiber
#

the undefined isn't important, no, but i need a way to somehow type it properly so ST & CT infer correctly

delicate jacinth
#

seems like the main issue is that the result of applying HasKeyPrefix is not always assignable to z.ZodRawShape (which is how schema is typed in the implementation signature). the 'bad key no prefix' error result is at least part of the root cause of that (because that string literal type is not assignable to all of the possible properties of z.ZodRawShape, i guess)

#

i think i'd just drop the cute error message and instead reduce to never in the bad cases:

still creekBOT
#
mkantor#0

Preview:```ts
...
type HasKeyPrefix<
T extends z.ZodRawShape,
P extends string

= {
[K in keyof T]: K extends ${P}${string}
? T[K]
: never
}
...```

south fiber
#

i'm happy with never, iirc i changed the type as i was trying to debug an earlier error and just hadn't changed it back

#

so now there is a new error on L13 in your above playground

#

the error there is simple enough, but it has a knock-on effect that i am unsure of how to deal with properly

#

what's the appropriate way to write the type when undefined has been thrown into the mix?

#

i'm starting to think an extra overload might be the way to go?

#

something like this, but it doesn't quite work

#

The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.

#

check it out, that message is back

#

i believe this means the overload is not catching this particular variation, and it is saying the implementation would work, but it's not usable

delicate jacinth
south fiber
#

i genuinely have no idea how i'm supposed to proceed with this lol

#

separating into another overload sounds like a simple fix, but TS isn't able to narrow it seems

delicate jacinth
#

sorry but there's kinda just too much going on there for me to easily see what you're trying to do, so i'm going to need a little help. let's take the errors one at a time. for the server2 call, which of the overload signatures do you want it to match?

#

the second one is my guess?

south fiber
#

from the latest tspg?

#

hang on let me get it open

delicate jacinth
#

yes. here's one i reduced a bit already:

still creekBOT
#
mkantor#0

Preview:```ts
import {z} from "zod"

type RuntimeValues = string
type MatchedRuntime<T> = {
[K in keyof T]: RuntimeValues
}
type HasKeyPrefix<
T extends z.ZodRawShape,
P extends string

= {
[K in keyof T]: K extends ${P}${string}
? T[K]
: never
...```

south fiber
#

yeah ok let's work from your reduced one

#

L38 should match L23 in the second overload?

delicate jacinth
#

are you saying that's what you're trying to make happen?

south fiber
#

btw how come you removed the base implementation for the overloads

#

can you do that in general?

delicate jacinth
#

it doesn't matter

#

you're getting type errors, so we can ignore the runtime stuff

#

these are also declare functions now

#

just trying to stay focused and make things easier to see at a glance

south fiber
#

yeah no problem, i didn't know you could do that

delicate jacinth
#

i think you're saying you want this to typecheck?

still creekBOT
#
mkantor#0

Preview:```ts
import {z} from "zod"

type RuntimeValues = string
type MatchedRuntime<T> = {
[K in keyof T]: RuntimeValues
}
type HasKeyPrefix<
T extends z.ZodRawShape,
P extends string

= {
[K in keyof T]: K extends ${P}${string}
? T[K]
: never
...```

delicate jacinth
#

that's just the second overload on its own (and therefore no longer an overload)

#

and in that simpler example you can see the error more clearly. it seems your conditional type isn't behaving the way you expect

south fiber
#

yeah, i think your analysis is on point

#

i don't see anything wrong with that type though

delicate jacinth
#

since it looks like CT is getting inferred as z.ZodRawShape (which you can see by hovering over the call), it reduces to this:

still creekBOT
#
mkantor#0

Preview:```ts
import {z} from "zod"

type HasKeyPrefix<
T extends z.ZodRawShape,
P extends string

= {
[K in keyof T]: K extends ${P}${string}
? T[K]
: never
}

const x: HasKeyPrefix<z.ZodRawShape, "PRE_"> = {
PRE_A: z.string(),
PRE_B: z.boolean(),
}
...```

delicate jacinth
#

i suspect it's that inference which is the problem? like you don't expect the above to typecheck, yeah?

#

i.e. you want CT to be inferred as something more specific than z.ZodRawShape?

south fiber
#

yeah, if ZodRawShape reaches the function then it takes a supertype and messes it up

delicate jacinth
# still creek

cool, so let's pop back to this one and see if we can figure out why CT is being inferred so widely

south fiber
#

in your latest tspg i'd expect that to work?! i still don't see the problem with the type

delicate jacinth
#

oh wait you do?

#

no properties in in z.ZodRawShape will extend PRE_${string}, right?

#

i guess i should actually look at that type, 1 sec

south fiber
#

{ PRE_A: z.string(), PRE_B: z.boolean() }
that is a valid shape, it's a Record<string, ZodTypeAny>

delicate jacinth
#

sure, but it's not a valid HasKeyPrefix<z.ZodRawShape, 'PRE_'>

#

look at what that type computes to:

still creekBOT
#
mkantor#0

Preview:ts ... type Example = HasKeyPrefix<z.ZodRawShape, "PRE_"> ...

delicate jacinth
#

(Record<string, never>)

south fiber
#

i am not understanding why it is defaulting to never

delicate jacinth
#

HasKeyPrefix is mapping over the keys of z.ZodRawShape, which is just string. string does not extend `PRE_${string}`, so the result of that conditional type is never

south fiber
#

they're all never

delicate jacinth
#

yeah, because none of them have any keys that start with 'PRE_'

#

here's one that isn't never:

still creekBOT
#
mkantor#0

Preview:```ts
import {z} from "zod"

type HasKeyPrefix<T, P extends string> = {
[K in keyof T]: K extends ${P}${string}
? T[K]
: never
}

type Example3 = HasKeyPrefix<
{PRE_foo: "whatever"},
"PRE_"

// ^?```

south fiber
#

oh i see...

delicate jacinth
#

if you just want to focus on working through these specific type errors that's fine (type tetris can be kinda fun), but in case it'd be more useful to take a step back: at the end of the day what are you trying to do here? why do you need to prove that the zod validators that people are passing in only have specific prefixes on their keys?

#

if you don't want to take a step back, maybe you can describe in english what HasKeyPrefix on its own is supposed to do? its name doesn't seem to match its definition from my understanding of it, and maybe that's part of the problem

south fiber
#

so checking string always ends up in never

#

meaning only literals (inferred values) are going to not return never

#

if literal is not available, the supertype is selected, and ends up as never

#

the type is supposed to ensure all keys start with that prefix

#

it'll be fine as long as i can guarantee the inference is passed in i think

delicate jacinth
south fiber
#

is it not being a mapped type a functionally different idea?

delicate jacinth
#

i'm not sure i understand the question, but that type does have different behavior from the mapped version. here's an example:

still creekBOT
#
type HasKeyPrefix1<T, P extends string> = { 
    [K in keyof T]: K extends `${P}${string}` 
        ? T[K] 
        : never 
    }
type HasKeyPrefix2<T, P extends string> = keyof T extends `${P}${string}` ? T : never

type Example1 = HasKeyPrefix1<{ PRE_foo: 'a', notPRE_bar: 'b' }, 'PRE_'>
//   ^? - type Example1 = {
//       PRE_foo: 'a';
//       notPRE_bar: never;
//   }

type Example2 = HasKeyPrefix2<{ PRE_foo: 'a', notPRE_bar: 'b' }, 'PRE_'>
//   ^? - type Example2 = never
delicate jacinth
#

wow, i was going to say i doubt that's enough to solve the server2 problem, but i was wrong. this works!

still creekBOT
#
mkantor#0

Preview:```ts
import {z} from "zod"

type RuntimeValues = string
type MatchedRuntime<T> = {
[K in keyof T]: RuntimeValues
}
type HasKeyPrefix<
T,
P extends string

= keyof T extends ${P}${string} ? T : never

declare function createEnv<
ST extends z.ZodRawSha
...```

delicate jacinth
#

i guess the different shape allows for better inference

south fiber
#

back in 5, i have to reset, things are crashing

delicate jacinth
#

oof

#

actually, looking all the way back here, does this change solve the remaining issues? looking again i suspect client1 is supposed to be a type error (because it uses the wrong prefixes)?

still creekBOT
#
mkantor#0

Preview:```ts
import {z} from "zod"

type RuntimeValues = string
type MatchedRuntime<T> = {
[K in keyof T]: RuntimeValues
}
type HasKeyPrefix<
T,
P extends string

= keyof T extends ${P}${string} ? T : never

function createEnv<ST extends z.ZodRawShape>(op
...```

delicate jacinth
#

i think i should go to sleep now actually, got an early start tomorrow. hopefully that helps. cheers!