1

So this works beautifully, using @next version of Typescript

function fork<
  Paths extends Partial<Paths>,
  OutputType extends keyof Paths
> (callback: () => OutputType, paths: Paths) {
  const result = callback()
  const path: () =>  ReturnType<Paths[OutputType]> = paths[result]
  const pathResult = path()

  return pathResult
}

const myResult = fork(() => 'bar', {
  foo: () => 'bip',
  bar: () => 123
})

Based on the returned string of first callback, the myResult gets correct typing.

However, when I do this small change:

function fork<
  Paths extends Partial<Paths>,
  OutputType extends keyof Paths
> (callback: () => OutputType, paths: Paths) {
  const result = callback()
  const path: (p: string) =>  ReturnType<Paths[OutputType]> = paths[result]
  const pathResult = path('foo')

  return pathResult
}

const bah = fork(() => 'bar', {
  foo: (p) => 'bip',
  bar: (p) => 123
})

Basically just adding an argument, the whole thing breaks with no good error message.

To look at this just paste the code into VS Code using the latest 2.8 (@next) version of Typescript.

2
  • Hm, it compiles here. You running this on Typescript 2.8-dev? In VS-code you have to manually change to that... bottom right. But to answer question. I do not mean to be recursive, I just see that without this part I am not able to do this part: ReturnType<Paths[OutputType] Commented Mar 4, 2018 at 19:09
  • Wops, had strict turned on, my bad Commented Mar 4, 2018 at 19:11

1 Answer 1

3

I think you might be running into some quirk with the "implicit any" typing for function parameters. I almost always use the --noImplicitAny compiler option, and your bug seems to go away when I give an explicit type annotation to the parameters here:

const bah = fork(() => 'bar', {
  foo: (p: any) => 'bip',
  bar: (p: any) => 123
})

That being said, the type parameter constraints you're using are bizarre. I don't know what Paths extends Partial<Paths> is supposed to achieve. Personally I'd be inclined to type your function like this:

function fork<T, K extends keyof T>(
  callback: () => K, 
  paths: {[P in keyof T]: (p: string)=>T[P]} 
): T[K] {
  const result = callback()
  const path = paths[result];
  const pathResult = path('foo')
  return pathResult
}

which uses inference from mapped types instead of the predefined conditional type ReturnType<>. There's nothing wrong with using conditional types, but since they're not officially available I don't know if it's stable enough to use. But in any case you should probably define the type of paths in a way that lets TypeScript know they should have functions as properties.

Hope that's useful. Good luck!

Sign up to request clarification or add additional context in comments.

5 Comments

The problem is that this: const bah = fork(() => 'bar', { foo: p => 'bip', bar: p => 123 }) still causes an error, the compiler refuses to see p should be string and goes bats***t crazy with the errors : Type '"bar"' is not assignable to type 'never'
The example above is a bit simplified to be able to get started on the problem :) I hope to solve the actual problem by finding a solution to the example in the issue here. Thanks for the input, this certainly changed my perspective on defining the initial types. Though yeah, there is still an error with this change as noted by @TitianCernicova-Dragomir
I understand that p => 'bip' gives an error; I was saying the error goes away if you do (p: any) => 'bip' or (p: string) => 'bip'. Contextual parameter typing and generics don't play well together sometimes. Is there a reason you can't explicitly annotate the function parameters?
Ah, sorry, misunderstood. I could get away with typing it, though I think it would be confusing for the consumer of the API. But a bigger problem is the composition. fork(() => 'foo', { foo: someFunction }). We reference a function where function someFunction (p: string) {}. This is explicitly typed now, but Typescript will not be able to verify that the composition is correct, as the passed in argument might differ than the expected type.
Is that a different question, or relevant information you left out of the stated question?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.