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

Docs: documented use of function return type conditional on argument type is impractical #1931

Closed
msbit opened this issue Jul 11, 2021 · 5 comments

Comments

@msbit
Copy link

msbit commented Jul 11, 2021

Page URL:

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html

Issue:

The example showing use of return type conditional on argument type near

We can then use that conditional type to simplify our overloads down to a single function with no overloads.
will compile, but as indicated by the trajectory of microsoft/TypeScript#24929 a type safe function body is not possible.

Recommended Fix:

I can see a few options, either:

  • provide a type safe implementation (which is seemingly not possible)
  • provide an implementation with one of the unsafe workarounds (returning as any, reinstating the overloads, possibly others)
  • remove the specific use of conditional types as a return type dependent on an argument type (preferably with a specific call out that this is not supported, for those who go looking)
@edokeh
Copy link

edokeh commented Nov 17, 2021

+1

spent much time on this impossible function body

@orta
Copy link
Contributor

orta commented Nov 17, 2021

Yeah, makes sense, we should probably swap it out with:

```ts twoslash
interface IdLabel {
  id: number /* some fields */;
}
interface NameLabel {
  name: string /* other fields */;
}
type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;
// ---cut---
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
  if (typeof idOrName === "number" ) {
    return { id: idOrName } as NameOrId<T>
  } else {
    return { name: idOrName } as NameOrId<T>
  }
}

let a = createLabel("typescript");
//  ^?

let b = createLabel(2.8);
//  ^?

let c = createLabel(Math.random() ? "hello" : 42);
//  ^?
```

@whschultz
Copy link

Yeah, makes sense, we should probably swap it out with:

interface IdLabel {
  id: number /* some fields */;
}
interface NameLabel {
  name: string /* other fields */;
}
type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;
// ---cut---
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
  if (typeof idOrName === "number" ) {
    return { id: idOrName } as NameOrId<T>
  } else {
    return { name: idOrName } as NameOrId<T>
  }
}

let a = createLabel("typescript");
//  ^?

let b = createLabel(2.8);
//  ^?

let c = createLabel(Math.random() ? "hello" : 42);
//  ^?

I find the following works just fine:

interface IdLabel {
    id: number /* some fields */;
}
interface NameLabel {
    name: string /* other fields */;
}

// If you pass a number, you get `IdLabel`
function createLabel(idOrName: number): IdLabel;
// If you pass a string, you get `NameLabel`
function createLabel(idOrName: string): NameLabel;
// If you pass an ambiguous type, you get an ambiguous type.
// Yes, this extra overload needs to be here, if you intend to be
// able to call it this way.  Otherwise, the implementation signature
// isn't externally visible.
function createLabel(idOrName: number | string): IdLabel | NameLabel;

// The implementation has to be able to handle all of the above
// defined possibilities.
function createLabel(idOrName: number | string): IdLabel | NameLabel {
    if (typeof idOrName === "number") {
        return { id: idOrName };
    } else {
        return { name: idOrName };
    }
}

const a: NameLabel = createLabel("typescript");
const b: IdLabel = createLabel(2.8);
const c: NameLabel | IdLabel = createLabel((Math.random() >= 0.5) ? "hello" : 42);

@wbt
Copy link

wbt commented May 4, 2022

The prime example given in official example documentation for conditional types is not implementable.
The simplest implementation of that Deferred Conditional Type example is:

const getID = function<T extends boolean>(fancy: T): T extends true ? string : number {
	if(fancy) {
		return 'StringID';
	} else {
		return 5;
	}
};

but that fails to compile, even though on lines 3 and 5 where the error occurs the true/false value of 'fancy' is known at compile-time and that should permit TypeScript to figure out the expected type of the return value.

#24929 remaining closed suggest this won't be fixed and it's a big sore spot when trying to implement something like the example.

@typescript-bot
Copy link
Collaborator

Hello! As per #2804, we are automatically closing all open issues. Please see #2804 for a description of what issues and PRs can be accepted going forward.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Apr 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants