Inferred Type Parameter from a Fixed Union is Different from a Parameter that is a Fixed Union

So this took me embarrassingly long to figure out how to articulate, hoping it saves someone a lot of time
export const FixedInferredType = <T extends QueryRoutes>(name: T) =>
export const FixedInferredType = <T extends QueryRoutes>(name: T) =>
export const ParameterAsUnion = (name: QueryRoutes) =>
export const ParameterAsUnion = (name: QueryRoutes) =>
These are very meaningfully different in that if you pass in a name to the second one it doesn't narrow the return type at all, you'll get a type error if you don't explicitly type guard the possibilities (even if all the inputs you are pulling from are strongly typed and would not error) Even though with both you'll get an error if you pass in a random value Is this obvious advice that exists somewhere? trying to figure out how to explain this to my team or if there is just some resource i'm missing. I guess if you're building types for DX like I am, then you should be inferring or else you're not going to get the narrowing you really want. --- The Copilot output that made it click It makes sense to me and explains why I couldn't get the specificity in places where I really wanted it.
The fundamental difference between these two lies in how TypeScript infers types:

export const FixedInferredType = <T extends QueryRoutes>(name: T) =>: This is a generic function. The type T is inferred based on the argument passed to the function. If you call FixedInferredType("getBusinesses"), TypeScript infers that T is "getBusinesses", not just any member of QueryRoutes. This allows for more precise type checking and intellisense within the function body.

export const ParameterAsUnion = (name: QueryRoutes) =>: This is a non-generic function. The type of name is QueryRoutes, which is a union of several string literals. TypeScript only knows that name is some member of QueryRoutes, but it doesn't know which specific member it is. This results in less precise type checking and intellisense within the function body.
The fundamental difference between these two lies in how TypeScript infers types:

export const FixedInferredType = <T extends QueryRoutes>(name: T) =>: This is a generic function. The type T is inferred based on the argument passed to the function. If you call FixedInferredType("getBusinesses"), TypeScript infers that T is "getBusinesses", not just any member of QueryRoutes. This allows for more precise type checking and intellisense within the function body.

export const ParameterAsUnion = (name: QueryRoutes) =>: This is a non-generic function. The type of name is QueryRoutes, which is a union of several string literals. TypeScript only knows that name is some member of QueryRoutes, but it doesn't know which specific member it is. This results in less precise type checking and intellisense within the function body.
1 Reply
tendies123
tendies1237mo ago
I think I was running into this problem a lot because I like to program in itnerfaces so all of my union types would reach methods in pojos and pull inputs from those pojos that all had the same params for input (less work than it sounds honestly) But they had different return types (duh) So those different return types were fucking me in the union case and I couln't figure it out Because the union was playing nice for all the inputs (Since my inputs were the same) But suddenly for outputs i was getting recked And typescript's error system isn't as good as rust so it iddn't poitn this out to me