#Working on a tricky type problem with conditional return types + generics.

11 messages · Page 1 of 1 (latest)

high igloo
#

I can't use TypeScript to evaluate the conditional when S is generic, so it ends up unifying both branches.

Is this a fundamental TS limitation, or is there a pattern to make this work without casts at the generic call site?

Here is the complete reduced example: https://www.typescriptlang.org/play/#code/PTAEFkEsDtIWwIYBtQCUCmBnArkgLqHgJ4AO6AUAMZIKaZpa54A8AKgDSgCioAvNwCcBAewEA+UAG9yoUCQGQAbgjzpQlYdEx4B2SnlEAKGbLkLlq0APQIAJpqRFQAfWEBrAFygARsOFIbaHYTWXklFTVrOwcnZ2UkbHQAfi8OELNwyyj7aEcXdCFRFO4TAEopAF9yE20VSEpQAHk3Nk4efi5C8UN4xNTSrwwcfFbuCWlTK3Q8bAFoUGh0AHcGYZYOMcMdRM5e9E5saFt0ADMYdFtSgG4TKpq8OobOgVH2wRFugo+vLgHVple43S1hmcwWy3+Iw2XDEhhOyEw+1Ah2OZ0Wtk4X1E11u1VkkEwzUMfzwAAsCaAKUMARtFooCkDJiDZvMyQSAHSuNw3WR3fGYZ7Erxs+hUxgjOkFNqM0zMsEAQhFnPcPNAfOR0CWAgQJCFoFYUnSkBOoEMivJmGVbnKZJEKyVzixAlVsjlrItnL2oFo+tVVTuIH1pDU2DwkCQkDDWHIxDIoAAqprtSRqVDpXx9aB0AAPVRHeiplgwE4FBPppIJ0CpG7kQONekCJDCOyYcgnQ76SCaLPZyjoEhhzQAJkLrx7eds9Geogz0+6JhO0C8xL4EgACiI4AT0GwxOQ-hvhFvEcxR4mtTrR9CxNKxDX29BO92c32B13oCPxes2uP0Pn3jOHRdLCC5Lqa5S8BIrD7oMX7MOeyZXtK0o1oGACScAkAEcB-g8g7QG2Hb4T2r74Z+axjjmE5Tl0s7AcYsiLsuEFQaAAA+oCHseO6sHufxIWM7GcZu26nnB14yjoTgTKYGhaAQ1hrBmi7Ei6lImoYilMJS8kII+6DCCaXHbuUMlMtMLJTGs6STOyZJ-oYPQsaaig6bU+mGZCBAVq5sFrOyRKKKUwU2aY7KUColCko56DOYW7KCrF5Q+sZJ4CTCd7pFU5mgvMmlfm5DweSahagBWWn4FWXkBW4+VrMF3oFuJKG3OokWkqasWGjllnxYljXcDivLkAGYCAKDkoAAOqiG49AALTqJolAgmosbRnJ2hWAAjBmL79mRhaOc5qXoOyin+PShhbUOADMwU3IGpgAHoVidYkUdA2BwN4UoAeIe5UJom0CEOu29vt77kUwR2rsJR7bmdWAXeghjxUS113fdtZgM9r0iWlcGfd9v1zmIEgLYcF4kGQtjytUG0KTdYOkZDh0rpBoAAESkugSBNpzOKPbIL1ecw2gKNAADmbTAdUgaADLkcPeNhoALZLf4FPUhDBnICDajhqgCOQtBEI+oAPk+8zWAAjok2jMAAyrCB74zuo5OzKbokRDw5syx6QlngUWGJzwA6pAAt2Tz0COQIzkCOyABWmCaCuPoO6UZQmIGc6pDrADk7tCe7MsfDef1iPnlL0NAwgELQmCQJL0AIMrq3CNrcaF3BDul6IlcjXLYCAAVkgDwf1NM168IKKq+CFz0BF2jG5gpsNBbxE23beCTZGpIAMK0CwTt6m97tk91Uy5d7b6+1+MOQQH0zB6H4eR-ZMeafHScpzHyX0BnZQBqnx7mTG4FQgA

// I have a function that wraps execution in Result<T, E>
// It should also "passthrough" if the fn already returns Result (no double-wrap)

type UnwrapResult<T, E> = T extends Result<infer U, E> ? U : T;
function exception2Result<T, E extends Error = Error>(
  fn: () => Promise<T>
): Promise<Result<UnwrapResult<T, E>, E>>;

const r = exception2Result(() => Promise.resolve(Result.Ok(123)));

async function request<S>(): Promise<Result<S>> {
  return exception2Result(() => fetch("/api").then(r => r.json() as S));
  // type-error without the cast
}
cerulean wren
#

you should send the playground link directly without link shorteners, so the playground can be embedded here by the bot

high igloo
#

Thanks --- thought it will be a bit messy

cerulean wren
#

you'll have to send it in a new message to get the bot to embed it

wraith plank
#

can you show a more realistic example of how you'd use the non-async overload signature?

#

is there a particular reason you're modeling Result this way rather than as a discriminated union?

#

the flattening behavior also seems unsafe and i don't think i understand its utility. a more realistic example of that could help too

high igloo
wraith plank
#

so i have opinions on a lot of different aspects of this, but i think the problem you want to focus on comes down to the auto-flattening behavior so i'll try to only comment on that unless you also want feedback on other stuff (i may slip up though 😄)…

#

what if S in request is itself a Result and i'm expecting a Promise<Result<Result<string>>> or somesuch back? that's what the type checker is complaining about. it seems that you'd need to use UnwrapResult in every generic function signature where you're calling exception2Result, like this:

wicked pierBOT
#
mkantor#0

Preview:```ts
...
async function request<S>(): Promise<
Result<UnwrapResult<S, Error>>

{
return exception2Result(() =>
fetch("/api").then(r => r.json() as S)
)
}```