microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.71k stars 12.45k forks source link

`satisfies` for return types #59577

Open btoo opened 2 months ago

btoo commented 2 months ago

πŸ” Search Terms

satisfies return

βœ… Viability Checklist

⭐ Suggestion

type AcceptableType = string | number | boolean | undefined | null | object[]

/** `const fn: () => "asdf" | 8 | undefined` */
const fn = (): satisfies AcceptableType => {
  if (someCondition) {
    return "asdf"
  } else if(otherCondition) {
    return 8
  } else {
    return undefined
  }
}

πŸ“ƒ Motivating Example

function getResult(): satisfies { a: string, b: number } {
  // type-errors until `satisfies` condition is met
  return {
    // type-hints and type-checking for:
    // (property) a: string
    // (property) b: number
  }
}

πŸ’» Use Cases

The satisfies operator has been immensely useful for ensuring expressions match some wider type while also inferring their narrower type

However, because satisfies works only on expressions, getting the same behavior on function return types requires workarounds with several drawbacks. For example, if a function has multiple return values, we need to type satisfies SomeType for every single one of them, which is not only cumbersome but also prone to human error

/** `const fn: () => "asdf" | 8 | undefined` */
const fn = () => {
  if (someCondition) {
    return "asdf" satisfies AcceptableType
  } else if(otherCondition) {
    return 8 satisfies AcceptableType
  } else {
    return undefined satisfies AcceptableType
  }
}

playground link

This style is also incompatible with codebases that prefer/require their contributors to declare the return type of their functions (e.g. through eslint)

Another workaround exists by using satisfies on the entire function:

const fn = (() => { /* ... */ }) satisfies () => AcceptableType

playground link

but having to parenthesize the entire function just to be able to operate on it as an expression and then include the function signature in the satisfies type is at best impractical and at worst not even an option, in the case of function fn() { /* ... */ } declarations. Of course, function declarations will be available as expressions after the declaration, at which point satisfies can be used, but not to its full extent because the type-inference will no longer be able to guide the implementation of the function declaration from within the function body

// this correctly errors but...
getResult satisfies () => { a: string, b: number }

function getResult() {
  return {
    // you don't get any type-hints or type-checking here
  }
}

playground link

Being able to specify that the return type of a function satisfies a type would resolve all of the above drawbacks

MartinJohns commented 2 months ago

Duplicate of #51556.

btoo commented 2 months ago

Duplicate of #51556.

@MartinJohns i've seen that issue and i don't think the feature request is the same. #51556 seeks to be able to ensure that an entire function signature satisfies some type, but #59577 seeks only to specify that the function return type (not the entire function signature) satisfies some type

MartinJohns commented 2 months ago

IMO that's a negligible difference. Both issues want the same: The function must satisfy a type. You just don't care about the arguments, so you'd check for satisfies (...args: any[]) => WhateverType. And your problem would be solved with the other approach, while the other wouldn't be solved with yours.

But it's up to the maintainers to decide if it's a duplicate or not.

uhyo commented 2 months ago

The same request was made before: #52195

Comment by @RyanCavanaugh:

While I agree with that as being an upside, I don't think it merits the trade-off compared to making a type psuedo-operator that only works in certain places. Tracking the use case at #51556

I would still really love to see this specific syntax to be accepted though πŸ₯²

btoo commented 2 months ago

funny enough, i also requested a proto-version of https://github.com/microsoft/TypeScript/issues/52195 in https://github.com/microsoft/TypeScript/issues/7481#issuecomment-787399912 before the satisfies operator was made available

tbh i still would absolutely love to see https://github.com/microsoft/TypeScript/issues/52195 come to fruition, but maybe the scope/lift for #59577 is smaller and therefore more viable

as an aside, i can see in https://github.com/microsoft/TypeScript/issues/58918#issue-2360893787 and plenty of other feature requests there's a desire to co-opt satisfies in many other contexts. seems like the semantics of an inference-friendly opaque type is proving to be a powerful one. here's to hoping the maintainers give this a real chance 🀞