Skip to content

Types of optional arguments not inferred correctly #56545

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

Closed
lsnow99 opened this issue Nov 26, 2023 · 3 comments
Closed

Types of optional arguments not inferred correctly #56545

lsnow99 opened this issue Nov 26, 2023 · 3 comments
Labels
Duplicate An existing issue was already created

Comments

@lsnow99
Copy link

lsnow99 commented Nov 26, 2023

🔎 Search Terms

"optional" "arguments" "inference" "generic" "narrowing" "parameters"

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about optional parameters

(tested on latest minor version of v3, v4, and v5)

⏯ Playground Link

https://www.typescriptlang.org/play?target=99&jsx=0&ts=5.3.2#code/PTAECECcHsGsFMB2p4A8CGBbADgG3qAO4CWALgBagDG0iAJmcberqKQJ7bwDOAULwDMAroiqkmyTOwBiIsRIAUqAPwAuUIiGYARvEgBKdRy7QBoVClSkkdbqBF14A4onh1Qy0N1KQXAc1B1TR09UABvXlAo0GIzJVAAXiT7eicXN31wyOicmkRvUAAvRPNQEFAASVJQWERoQjtjeFNSpISUx2dXd3I9eAAaUG0hau9iXFZCdERSRuhQSHhSIUhkdA0tXUhsnKjygGFoR1ABaEgiXuQLYjs60aFsPGI3Hd3F5dXQACIAFU4CACCXwA3K8AL4oXDcAgRXbRA5HAinc6ES6lG5eB5PF5wqLvFZXUAAKlAACZgWUwABRDA4fCDdjQITUaYLJYE0DrBgCAR9GZsf6gXqLcG8MH8PIFRbcIS4UgARnU3l8iAC7SkslE4loCn0FJy5QqAHJ3GguGI2OQMbQCKR5roBVx3Og7Otlf5eJLqtLZaRSUFNqF1TI5NrEAoAKx6g1gH5Wuw2x08ahnRZiXDsfj8coABXQkHELAzRDOsH8liweHgEtoBWw+cLE3YAHVS+X2gpOuhfQA1FhCeBqDYhAyJAB8WSisVAnac3blfdwA8SyQcaW6mVh0Xxny+dytqq+2Qh8ChMNeO+QXd7-erUXF4t45QAEn0htAKJzFknnbdA+cAB8vB8fxBlRYgqEoDF0GqfAXWqGhIDTUhBmGao7k5OxlVoAIXRiBCmVwdxdE9WtqjQeUSnrAtiCLFs21VBR5X0Uj8nI1BSSohtaKbVtIDLRiWKzcooDgJAKzpatPVwF07BzAAeAAtCdYS9SAhDEM4Z0KdRFM3bJJWgfAADpcGgPwFEKFjQAffgmlAH4SjCTl1HlR8vVAaBsDDFgSnkgANEofjHWcBHnUhFwHId-MyBIVOyadQvCyKCDaDp1wySdt3ZT5XEIUAFMcoC1y6NwQpK9I6GsmzIWhLK8Ry5A8oK+SitAfyQuvBdb2q2ynzAcAP0oFoKB4Ah81tf5fxatqKu6MdjNAfrKkcOjBlGk5iEgApE24chCOIyanUwpbc1a0AgOCLYJ2mdwNuhPJ3ETJpf2WwqLvS0q6AW0AASheZ0CoKh4G89BtHwEtZUO5btCGoZ4HLF6TveoCOo+lHPsqsdWIKBUSi8nzcCYgAWFiPL9fHvIkFhdSAA

💻 Code

// Broken example with conditional types

function myFunction(x?: number): typeof x extends undefined ? string : number {
    if (x === undefined) {
        const z = x // It knows typeof x === undefined here, but still wants to return a number
        // Code for when x is not supplied
        return "Type A";
    } else {
        // Code for when x is supplied
        return x * 2; // Example, you can return a different type here
    }
}

const result1: string = myFunction();     // I'd expect this one to be typed as a string
const result2: number = myFunction(5);    // This one types correctly



// Partially working example

const partiallyWorking = (defaultValue?: number) => {
  if (defaultValue === undefined) {
    return "nothing"
  } else {
    return defaultValue
  }
}

// Here both are typed as number | string, which is at least correct, but not as strong as it could be
const ex1 = partiallyWorking(1)
const ex2 = partiallyWorking()



// Broken example

class P<Z> {
 constructor (z: Z) {
  console.log(z)
 }
}

type T = { a: 1}

const optional = <X = T>(defaultValue?: X) => {
  if (defaultValue === undefined) {
    return new P<T | undefined>(undefined)
  } else {
    return new P<T | X>(defaultValue)
  }
}

// Both of these are typed as P<T | undefined>. 
// Ideally, the first one should be typed as 
// P<T | number> and the second one typed as
// P<T | undefined>. Also acceptable would be
// both being typed as P<T | X> | P<T | undefined>
const t1 = optional(14)
const t2 = optional()

🙁 Actual behavior

Specifically in the third case, the return type was incorrectly narrowed to the wrong value.

🙂 Expected behavior

The function's return type should either be the union of the two possible return cases, or it should be narrowed based on the value of the optional argument.

Additional information about the issue

No response

@lsnow99 lsnow99 changed the title Types of Optional Arguments not Inferred Correctly Types of optional arguments not inferred correctly Nov 26, 2023
@MartinJohns
Copy link
Contributor

Sounds like a duplicate of #33912.

@gabritto
Copy link
Member

There are a few small things at play here, but the big thing is mainly #33912. Currently, TypeScript doesn't support annotating a function's return type with a conditional type, and relating control flow types to that return type.

The other small things to note here:

  • typeof is eager. That means that when you write function myFunction(x?: number): typeof x extends undefined ? string : number { ... }, TypeScript will eagerly resolve typeof x to whatever type x has at that position, which in this case is number | undefined. So effectively, this is the same as if you had written function myFunction(x?: number): number | undefined extends undefined ? string : number { ... }. The conditional type in turn resolves to its false branch, number. So, in the end, it's as if you had written function myFunction(x?: number): number { ... }.

  • The broken example of a function returning P<T | undefined> works that way because TypeScript thinks the type parameter Z doesn't affect assignability of P<Z>, so in fact, all of the following are assignable to each other: P<number>, P<string>, P<unknown>.
    Therefore, when TypeScript infers a return type for optional, the return type is the union of the types of both return statements, i.e. P<T | undefined> | P<T | X>, but that in turn simplifies to just P<T | undefined>.
    If you change the P class to one where TypeScript thinks the type parameter Z will influence assignability, that simplification goes away and the return type is inferred as P<T | undefined> | P<T | X>.
    Example in playground

@gabritto gabritto added the Duplicate An existing issue was already created label Nov 27, 2023
@gabritto gabritto closed this as not planned Won't fix, can't repro, duplicate, stale Jan 3, 2024
@lsnow99
Copy link
Author

lsnow99 commented Jan 3, 2024

Thank you

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

3 participants