Skip to content

Allow property if at least one union item it #56752

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
6 tasks done
remcohaszing opened this issue Dec 12, 2023 · 3 comments
Closed
6 tasks done

Allow property if at least one union item it #56752

remcohaszing opened this issue Dec 12, 2023 · 3 comments
Labels
Duplicate An existing issue was already created

Comments

@remcohaszing
Copy link

πŸ” Search Terms

union optional property access

βœ… Viability Checklist

⭐ Suggestion

If a union contains multiple non-nullish values, I expect it to be ok to access the property. This would then yield the type defined by one of the union members, or undefined.

πŸ“ƒ Motivating Example

interface Person {
  hands: string[]
}

interface Cat {
  paws: string[]
}

declare const maybePerson: Person | undefined

const hands1 = maybePerson?.hands
//    ^^^^^^ This is `string[] | undefined`

declare const creature: Cat | Person

const hands2 = creature.hands
//    ^^^^^^ I expect this to be `string[] | undefined` too

// This often leads to awkward constructs with `in` checks
if('hands' in creature) {
  creature.hands.map(hand => hand)
}

// where at runtime optional chaining would be fine.
creature.hands?.map(hand => hand)

πŸ’» Use Cases

I often find myself writing:

if ('key' in object) {
  doSomething(object.key)
}

Or even:

if (object && 'key' in object) {
  doSomething(object.key)
}

Or worse, nested constructs:

if (object && 'key' in object && typeof object.key === 'object' && object.key != null || 'nested' in object.key) {
  doSomething(object.key.nested)
}

This is purely to make TypeScript happy. The following is just as safe:

if(object?.key?.nested) {
  doSomething(object.key.nested)
}
@MartinJohns
Copy link
Contributor

Duplicate of #38842 (and others).

This is intentionally not allowed.

@fatcerberus
Copy link

This is intentionally not allowed.

And the reason why is

interface Monke {
    hands: number
}
interface Cat {
    paws: number;
}

const mutant = { paws: 4, hands: "now with opposable thumbs" };
const animal: Monke | Cat = mutant;  // ok, extra property allowed
console.log(animal.hands)  // proposed: number | undefined, but actually a string!

A 'key' in object check will work here, but that's an intentional tradeoff because you have to opt in to unsoundness.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Dec 12, 2023
@remcohaszing
Copy link
Author

I thought the following was allowed:

interface Monke {
    hands: number
}
interface Cat {
    paws: number;
}

const mutant = { paws: 4, hands: "now with opposable thumbs" };
const animal: Monke | Cat = mutant;
if ('hands' in animal) {
  console.log(animal.hands)
}

where animal.hands would be of type number inside the if-statement. It turns out I was wrong. This is allowed, but animal.hands is of type unknown.

Although I still think the current behaviour is sometimes annoying to work with, I see the reasoning for having it this way. I’ll close this issue.

@remcohaszing remcohaszing closed this as not planned Won't fix, can't repro, duplicate, stale Dec 12, 2023
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