Skip to content

Optional chaining discards contextual type data #57645

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
dgetu opened this issue Mar 5, 2024 · 4 comments
Closed

Optional chaining discards contextual type data #57645

dgetu opened this issue Mar 5, 2024 · 4 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@dgetu
Copy link
Member

dgetu commented Mar 5, 2024

🔎 Search Terms

contextual type
optional chaining
subtype reduction
control flow analysis

🕗 Version & Regression Information

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

Optional chaining was introduced in 3.7, and 3.7 has the same behavior.

⏯ Playground Link

https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgGIHt3IN4ChnIBGcUA-AFzICCUUcAngDwgCuAtodAHwDcuAvrgToQAZzDIYmZAF5kLEABMIMUBEXI4otNIA+8pSrWK+wGMgAUAQinpSAOmJQAlDnzIA9B+QRgYABbQktLoUEQkyP5ayABucAA2LCgABgrKqiDqye7CYhIScrYOTnwEXsgAKsii-ugs8RqcyGD0AA4paUaZitkELe2Vss1tEOjmYKXIueLhAF6UFgAelDR0TKwc3Mj6nRnqrjJcsejAGnJLlBUHR3gEBNPo8RD20FChFgBEAcDaWqLAAHMQGwIOBqrV6ooQAByCTCNitYBPD7OSb8PiCIA

💻 Code

interface Foo {
  bar?: Array<number>;
}
const foo = undefined as Foo | undefined;
if (!foo?.bar) {
  // either foo or bar has value `undefined`
  const t = foo?.bar;
  // T should be type `undefined`
  type T = typeof t;
  const baz: (x: Array<number> | undefined) => void = (x: T) => {
    console.error("this assignment shouldn't get past the type checker");
  };
}

🙁 Actual behavior

Type T has type Array<number> | undefined, even though the conditional implies that the value of t is falsy.

🙂 Expected behavior

The compiler should say that foo.bar is undefined in the scope of the then branch of the if statement.

Additional information about the issue

No response

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Mar 5, 2024
@RyanCavanaugh
Copy link
Member

Narrowing doesn't really have a way to represent "this or the other thing must be undefined" in a way that can be propagated around - it has to make a definitive determination about a particular variable.

@dgetu
Copy link
Member Author

dgetu commented Mar 5, 2024

I see. So when we have some type guard around a property access, e.g. foo.bar without optional chaining, whatever the type checker infers about bar can only be used to modify the contextual type of foo because foo is the only binding in scope?

If that's the case, I think the "either foo or bar is undefined" assertion can be reframed as "foo has type undefined | { bar: undefined }."

// I'm making the (probably false) assumption that TypeScript can recognize the law of excluded
// middle between Nullish<T> and NonNullable<T>
// `any` is a corner case, but I don't think that it needs to be handled any differently
type Nullish<T> = T & (null | undefined);
interface Foo {
  bar?: Array<number>;
}

const foo = undefined as Foo | undefined;
if (!foo?.bar) {
  type NarrowedFoo =
    // foo is nullish
    | Nullish<typeof foo>
    // xor foo.bar's type is nullish
    | { bar: Nullish<NonNullable<typeof foo>["bar"]> };
  // foo ought to have type `NarrowedFoo`, but this assignment causes a type checker error
  const narrowedFoo: NarrowedFoo = foo;
}

@fatcerberus
Copy link

I think the "either foo or bar is undefined" assertion can be reframed as "foo has type undefined | { bar: undefined }."

This would require #42384. Type narrowing operations on foo.bar currently don’t have any effect on the type of foo, except for the specific case of discriminated-union narrowing.

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Design Limitation" 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 Mar 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants