Typescript generic field selection assumes type incorrectly

I was working on a generic lib-like api for something I'm working on, and got stumped by unexpectedly wrong typescript generic field selection. E.g. if <Foo extends Something>(args: Foo), then you'd think args.field is typed as Foo['field'], but it's not, it's simplified down instead. Here's a minimal repro: https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABFApgZygHgCqJQD1TABM1EBvRAQwC4LFg446MAnGMAc0QF9eA+ABRVWnOtgCUFAFCJErFFBCskIzgDoqAbmk9p0iAgyJiVKFUQBeZOiiDKteo2aIARK4A0iAEYi673l4JHUMwNDgAGxR1CLhOQVNzdV9WYOkgA Is there known ways of getting around this without using as? It feels so wrong. E.g. here's what i have to do to fix it in my real code:
z.object({
functionName: z.literal(args.name as ToolArgs["name"]),
args: args.data.args as ToolArgs["data"]["args"],
responseData: makeToolResponseDataSchema(
args.data.internalResponseData as ToolArgs["data"]["internalResponseData"],
args.data.externalResponseData as ToolArgs["data"]["externalResponseData"]
),
})
z.object({
functionName: z.literal(args.name as ToolArgs["name"]),
args: args.data.args as ToolArgs["data"]["args"],
responseData: makeToolResponseDataSchema(
args.data.internalResponseData as ToolArgs["data"]["internalResponseData"],
args.data.externalResponseData as ToolArgs["data"]["externalResponseData"]
),
})
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
43 Replies
erik.gh
erik.gh6mo ago
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
erik.gh
erik.gh6mo ago
ok let me see
Arduano
ArduanoOP6mo ago
in my real code's context, the "buildComplex" is actually zod types
Arduano
ArduanoOP6mo ago
here's a full screenshot of my real code for context, if it's relevant
No description
erik.gh
erik.gh6mo ago
i mean you could still annotate it without losing any inference
Arduano
ArduanoOP6mo ago
as in, the return type?
erik.gh
erik.gh6mo ago
yes?
Arduano
ArduanoOP6mo ago
it's especially difficult here because I'd have to create a zod schema manually using types, and then again using code (the point of zod is to skip defining the types) using as is the lesser of the two evils ig, I was just wondering if there is a real idiomatic solution to this because it feels kinda wrong
erik.gh
erik.gh6mo ago
would you mind showing the simplify zod function?
Arduano
ArduanoOP6mo ago
oh right it's this cursed thing, because while I was prototyping, typescript was having even weirder deep issues that would be hard to explain. You can ignore this function, I was planning to remove it anyway
No description
Arduano
ArduanoOP6mo ago
(removed it just then)
erik.gh
erik.gh6mo ago
oh okay
Arduano
ArduanoOP6mo ago
That's the same as doing as though right, still have to do TBTA['data']['args'] manually
erik.gh
erik.gh6mo ago
i think the important part is that it does not force the type on the attribute
Arduano
ArduanoOP6mo ago
like your code can be shortened to this
No description
Arduano
ArduanoOP6mo ago
as far as I can tell
Arduano
ArduanoOP6mo ago
or this
No description
erik.gh
erik.gh6mo ago
yes you can also inline the type that does not make a difference i personally don't like type assertions. i try to dodge them as much as i can because in some way this forces an identifier or attribute to be of type x while not necessarily being of that type
Arduano
ArduanoOP6mo ago
wait I just noticed, you can replace as with satisfies, that's neat not perfect but cleaner
erik.gh
erik.gh6mo ago
yes i think that would in fact be the same as annotating it
Arduano
ArduanoOP6mo ago
actually no it's not
No description
Arduano
ArduanoOP6mo ago
doesn't type error here
erik.gh
erik.gh6mo ago
that's interesting i think the whole issue you are having is typescript narrowing the nested type of a generic too early
Arduano
ArduanoOP6mo ago
yeah basically
erik.gh
erik.gh6mo ago
so you will need to widen it in any form
Arduano
ArduanoOP6mo ago
in what way? unless you mean manual casting
erik.gh
erik.gh6mo ago
in a way that you tell typescript that rec.a is not actually of type { foo: string } but of type T['a']
Arduano
ArduanoOP6mo ago
it's annoying because this works as expected
No description
Arduano
ArduanoOP6mo ago
No description
Arduano
ArduanoOP6mo ago
but this doesn't
No description
Arduano
ArduanoOP6mo ago
hmm
erik.gh
erik.gh6mo ago
yes because it's the nested type of your object that causes typescript to narrow too early
Arduano
ArduanoOP6mo ago
oh, wdym? is there some specific property of the type where it decies to simplify/narrow, which I can force it to avoid?
erik.gh
erik.gh6mo ago
i think deep inference of generic objects is what causes your issues but there is no real way around that unless you were to split your object into multiple individually inferable function parameters that is my guess
Arduano
ArduanoOP6mo ago
yeah that would make sense, though it's not really feasible in my context I attempted it at some point though
erik.gh
erik.gh6mo ago
yeah that's what i thought i personally would just go with type annotated variables if that works
Arduano
ArduanoOP6mo ago
as is more concise but equally type safe for now if satisfies doesn't catch it then type annotated variables definitely don't either
erik.gh
erik.gh6mo ago
catch what?
Arduano
ArduanoOP6mo ago
invalid casting like here
erik.gh
erik.gh6mo ago
wait is args generic?
Arduano
ArduanoOP6mo ago
yeah you can see it here
Want results from more Discord servers?
Add your server