Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passed generic not giving exact return type in callbacks #58826

Closed
christianalfoni opened this issue Jun 11, 2024 · 7 comments
Closed

Passed generic not giving exact return type in callbacks #58826

christianalfoni opened this issue Jun 11, 2024 · 7 comments
Labels
Duplicate An existing issue was already created

Comments

@christianalfoni
Copy link

christianalfoni commented Jun 11, 2024

🔎 Search Terms

"exact return type", "generic return type", "return type excess property checks"

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about exact types, return types, generics and excess property checks

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABABwIYCdUFsDOAxcCAHgBUA+ACjUywC5ESBKRAbwF8BYAKFElgUQQARgUilKw+hWYBeMg2btu3KAE9kAU0QBZVSXVaZrbolOJgcOPRxR0MMAHNunLtwD0bxAEEciAO5aqAA2QYgA1mBwfigY2L4YWrYaqFAaACaIqL4aAB6o0Ihqmr5+MFAAFogOGmAadhA43NRxosS6+pqULOaW9ADkQhh9ADSIg+j9gwBefWyMylweiABCIFCF5TC+W4iR6xVaEFlafuU1G1o42FrVtfWIOyA46ZnxiOgaUCDoSEUa3MJWkR2gZKNJEHJEBQWCYzBYrIgBkNhrDTONJqgZs5GPNXItPKt1jBgIgAJKIXLIIIwCBlIKqQoGB5QUZlRA4EBpNI1en+ODoMLxByoewAkSEYF6UEUaT0EGaCHyaGonoIpHoEYq9GI6Z9bGMIA

💻 Code

function paramsFunc<T>(param: T) {}
function cbFunc<T>(cb: () => T) {}

type MyType = {
    foo: string
}

// As we all know params are treated as exact types with generics. "bar" gives an error
paramsFunc<MyType>({ foo: 'bar', bar: 'baz'})

// But this is not the case when the same generic is used as a return type. "bar" does NOT give an error
cbFunc<MyType>(() => ({
    foo: 'bar',
    bar: 'baz'
}))

// But if I explicitly type it, it suddenly works again. "bar" gives an error
cbFunc<MyType>((): MyType => ({
    foo: 'bar',
    bar: 'baz'
}))

🙁 Actual behavior

When passing an explicit generic, when that generic is used as a return type, the excess property check is not running

🙂 Expected behavior

When passing an explicit generic, the excess property check should run regardless of how that generic type is used

Additional information about the issue

As a library author you might provide callbacks to some public API of the library to generate values. These callbacks can have a return type that needs to be defined by the consumer of the API. In library world you typically pass required types as part of some constructor as the type is used to infer different aspects of consuming the API. Currently library authors has to either:

  1. Document that consumers has to both pass the type as a generic and explicitly use it as a return type on the callbacks
  2. Create an artificial utility function to trigger excess property check on the params to the utility function, just to return the param

An example of this would be:

type State = {
  status: 'ON'
} | {
  status: 'OFF'
}

type Event = {
  type: 'TOGGLE'
}

const transitions = createTransitions<State, Action>({
  ON: {
    TOGGLE: () => ({ status: 'OFF' })
  },
  OFF: {
    TOGGLE: () => ({ status: 'ON' })
  }
})

In this example the excess property check is not triggered for the return type of these callbacks, even though the State generic was explicitly defined. As mentioned in the points above, you would need to either have to remember to explicitly add the return type to the callbacks:

const transitions = createTransitions<State, Action>({
  ON: {
    TOGGLE: (): State => ({ status: 'OFF' })
  },
  OFF: {
    TOGGLE: (): State => ({ status: 'ON' })
  }
})

This is easily missed as there is nothing initially indicating that a type is missing. It is only when you start changing the types that you have unused and confusing properties that is not part of the type, but is not erroring either.

Or you can:

const transitions = createTransitions<State, Action>((transition) => ({
  ON: {
    TOGGLE: () => transition({ status: 'OFF' })
  },
  OFF: {
    TOGGLE: (): State => transition({ status: 'ON' })
  }
}))

But this makes the API more verbose than necessary and the utility function makes no sense for someone consuming the library without TypeScript.

Sorry if this is already an addressed issue, but it seems to sit between generics, exact return types and esxcess property checks... so difficult to pin down if this is a bug, something that requires changes to TS or is expected behaviour. If this is expected behaviour I want to argue that explicitly typed generics should trigger excess property checks.

Thanks a bunch for taking the time to look through my issue 😄

@MartinJohns
Copy link
Contributor

This is essentially a duplicate of #241. Check the linked issues.

@christianalfoni
Copy link
Author

Thanks for the reference @MartinJohns! My "Excess property checks on return types" search did not hit "widen return types" it seems 😂

But okay, great. I also see there was a design discussion on this #56965.

It seems it is planned to be fixed, hm

@christianalfoni
Copy link
Author

Okay, so I get a response that the referenced discussion (#56965) is related, but not a fix for this issue. So yeah, not sure how much attention this deserves and how to ensure it gets that deserved attention 🤔

@essenmitsosse
Copy link

Not sure if this is considered related, but this also doesn't work:

cbFunc<MyType>((): MyType => {
    const value = {
        foo: 'bar',
        bar: 'baz'
    }
    
    return value
})

@MartinJohns
Copy link
Contributor

@essenmitsosse That is working as intended. The type of your variables inferred to { foo: string; bar: string } due to the lack of type annotation, and when returning the value the compiler only checks compatibility. { foo: string; bar: string } is compatible with the type { foo: string }.

See the FAQ entry: (Indirect) Excess Properties Are OK

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jun 13, 2024
@RyanCavanaugh
Copy link
Member

It's probably time for my once-every-few-years attempt to look at #241 again and see what doesn't work these days

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants