#Strange behavior inferring return types of functions on contextually typed object literal

9 messages · Page 1 of 1 (latest)

compact dove
#

Hi, I'm not sure if this is a bug or somehow working as intended.

I have a function that takes an object literal as an argument, the properties of which may be functions/methods. I want to infer the return type of one function and use it as an argument to another. The consumer should be able to use contextual typing in function parameters.

For example:

function foo<R>(arg: {
  boz: (input: string) => R
  bar: (arg: R) => void
}) {}

foo({
  boz: (input) => {
    return { xyzzy: "quux" }
  },

  // arg: { xyzzy: string }  
  bar: (arg) => {},
}

This works fine, however if I move bar before boz in the object literal, it infers R as unknown:

foo({
  // arg: unknown
  bar: (arg) => {},
  
  boz: (input) => {
    return { xyzzy: "quux" }
  },
})

If I put bar first and remove the argument for boz it then infers correctly:

foo({
  // arg: { xyzzy: string }
  bar: (arg) => {},
  
  boz: () => {
    return { xyzzy: "quux" }
  },
})

If I put bar first and explicitly type arg, it then tries to constrain the return type of boz to that of arg. Just trying to understand what's going on here.

Playground link: https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABMOcA8AlAfACgIYBOA5gFyIDeAUIogEZwBeZOMYADiFGQM5QGtEAlIgC8WRBmp1CzQqQnCxiAG5wYAE0oBfYeS2VKKODio16TRC3adF40zUQEAplBAEk5RAA8AngwY+ZABEAI4gIF5BiPo0WgA0BjQA9EmIcmSevv6BiLz8YETRNFK0MpZythTx2oIGRiZSKWnEZOAA1mBwAO5gJWX4xJV6CcVmjMysHFBDUjTOru4U3n4BwWERUTHRCTp1qA3JqelLWau5fALRfQSyg6J21aN045YzDo4ubh7L2WvhkVdYjtakA

twilit voidBOT
#

@compact dove Here's a shortened URL of your playground link! You can remove the full link from your message.

hitchhiker42.0#0

Preview:```ts
function foo<R>(arg: {
boz: (input: string) => R
bar: (arg: R) => void
}) {}

foo({
boz: input => {
return {xyzzy: "quux"}
},

// arg: { xyzzy: string }
bar: arg => {},
})

foo({
// arg: unknown
bar: arg => {},

boz: input =>
...```

compact dove
#

Hmm, typing it like this gives the behavior I want, though the tooltip isn't ideal. Would still like to understand the above though.

#
  boz: (input: string) => R
  bar: <A extends R>(arg: A) => void
}) {}

foo({
  // arg: A extends { xyzzy: string }
  bar: (arg) => {
    arg.xyzzy
  },

  boz: (moo) => {
    return { xyzzy: 'quux' }
  }
})```
compact dove
#

Wow... I thought that was a great workaround... Nope, it just gets even weirder. It works fine in the above example. the first argument of bar above is of type A extends { xyzzy: string } great. But if I try to destructure it to { xyzzy } it breaks, and R becomes unknown!

#

I always break everything. I am why we can't have nice things. 😂

#

And of course the destructuring works just fine if bar comes after foo in the object literal.