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

Discriminate return type based on property in input object #31335

Closed
timsuchanek opened this issue May 10, 2019 · 3 comments
Closed

Discriminate return type based on property in input object #31335

timsuchanek opened this issue May 10, 2019 · 3 comments
Labels
Duplicate An existing issue was already created

Comments

@timsuchanek
Copy link
Contributor

timsuchanek commented May 10, 2019

TypeScript Version: 3.5.0-dev.20190509

Search Terms:
discriminate return type, keyof, extends

Expected behavior:
I want to discriminate the return type to "if a property in an object exists, return something different".

These are the 2 approaches I try, both don't work.

  1. Using 'select' extends keyof T Playground Link
type Args = {
  select?: boolean
}

interface Search {
  findIt<T extends Args>(a: T): 'select' extends keyof T ? number : boolean
}

function Search(): Search {
  const search = () => null
  search.findIt = <T extends Args>(a: T) => a.select !== undefined ? 1 : false
  return search
}

Error:

Type '{ (): any; findIt<T extends SomeArgs>(a: T): false | 1; }' is not assignable to type 'Search'.
  Types of property 'findIt' are incompatible.
    Type '<T extends SomeArgs>(a: T) => false | 1' is not assignable to type '<T extends SomeArgs>(a: Subset<T, SomeArgs>) => "select" extends keyof T ? number : boolean'.
      Type 'false | 1' is not assignable to type '"select" extends keyof T ? number : boolean'.
        Type 'false' is not assignable to type '"select" extends keyof T ? number : boolean'.ts(2322)
  1. Using T extends ArgsWithSelect Playground Link
type Args = {
  select?: boolean
}

type ArgsWithSelect = {
  select: boolean
}

interface Search {
  findIt<T extends Args>(a: T): T extends ArgsWithSelect ? number : boolean
}

function Search(): Search {
  const search = () => null
  search.findIt = <T extends Args>(a: T) => a.select === undefined ? false : 1
  return search
}

Error:

Type '{ (): any; findIt<T extends SomeArgs>(a: T): false | 1; }' is not assignable to type 'Search'.
  Types of property 'findIt' are incompatible.
    Type '<T extends SomeArgs>(a: T) => false | 1' is not assignable to type '<T extends SomeArgs>(a: T) => T extends SomeArgsWithSelect ? number : boolean'.
      Type 'false | 1' is not assignable to type 'T extends SomeArgsWithSelect ? number : boolean'.
        Type 'false' is not assignable to type 'T extends SomeArgsWithSelect ? number : boolean'

I'm running on [email protected].
Testing both interface definitions on a type level - they work perfectly. The only problem here is that TypeScript is not able to correctly infer the types from the ternary expression in the actual implementation.

In other words, I'm able to express this on a TypeSystem level, but I'm not able to provide an implementation that the compiler accepts.

Playground Link:

First Playground
Second Playground

Related Issues:
#28597

@jack-williams
Copy link
Collaborator

jack-williams commented May 10, 2019

Duplicate of #22735. Here is an example of why this wont work:

const search = Search();
const args: Args = { select: true };
const empty: {} = args;
const oops: boolean = search.findIt(empty); // evaluates to 1 at run time.

@timsuchanek
Copy link
Contributor Author

Thanks a lot for the answer! Looks like I'll have to go with an explicit return type annotation for now.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label May 13, 2019
@typescript-bot
Copy link
Collaborator

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

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

4 participants