#Type narrowing question

6 messages · Page 1 of 1 (latest)

swift bane
#

My coworker and I were playing around with a utility this week, and while we got a working solution, I wasn't satisfied with use of an explicit string literal key in its composition. Is it possible to narrow types here, with or without a type assertion?

I've posted the working first attempt, and my failing second attempt in a public gist here: https://gist.github.com/broguinn/209129b7a18134571497334b1993abe7

Edit: Typescript Playground link.

Apologies if this isn't the best format to share code snippets - it's my first post here. Thanks for your help.

green basalt
#

Your args type isn't right, it is extracting the union based on having a literal key k. I'd probably write this like:

export function Cases2<
  Key extends string,
  Union extends Record<Key, string>,
  U,
>(
  key: Key,
  args: {
    [c in Union[Key]]: (_: Extract<Union, Record<Key, c>>) => U;
  },
  data: Union
) {
  return args[data[key]](data as never);
}
swift bane
#

That's very helpful, thank you. Do you know why it is that Typescript can't infer the narrowing for data, and thus we must use the as never assertion?

green basalt
#

!:corr%

safe trailBOT
#
retsam19#0
`!retsam19:correspondence-problem`:

There's a particular pattern that is safe but hard for the Typescript compiler to handle, which I call the "correspondence problem":

const functionsWithArguments = [
  { func: (arg: string) => {}, arg: "foo" },
  { func: (arg: number) => {}, arg: 0 },
];

for (const { func, arg } of functionsWithArguments) {
  func(arg);
//     ^^^
// Argument of type 'string | number' is not assignable to parameter of type 'never'.
//   Type 'string' is not assignable to type 'never'.
}

The problem is that func is typed as (x: string) => void | (x: number) => void and arg is string | number, but the compiler can't prove that they "correspond": that, for example, arg is only a string when func accepts strings.

As far as the type are concerned, arg could be number, and func could be (arg: string) => void, and that would be a type-error. It's easy for us to see that that won't happen, but that requires understanding the program at a higher-level than the level the compiler operates.

Depending on the specifics there's sometimes clever fixes, but usually I recommend using a type assertion and ignoring the issue:

func(arg as never);
swift bane
#

!resolved