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

Indexing a type which was made via intersections produces an intersection of it's value types instead of a union #44108

Closed
iPherian opened this issue May 15, 2021 · 1 comment

Comments

@iPherian
Copy link

Bug Report

πŸ”Ž Search Terms

indexing intersections, generic object indexing

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about indexing intersections, etc

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

type PropsNotShared<LHS extends {}, RHS extends {}> = Omit<RHS, keyof LHS> &
  Omit<LHS, keyof RHS>;

export function handlePropsNotShared<LHS extends {}, RHS extends {}>(
  lhs: LHS,
  rhs: RHS
) {
  /**
   * [====================================]
   * Set everything up first:
   * [====================================]
   */

  /**
   * Assertion because it's just for checking types.
   */
  const notShared = {} as PropsNotShared<LHS, RHS>;
  /**
   * ts says the type of keyofNotShared is:
   *
   *   ```Exclude<keyof RHS, keyof LHS> | Exclude<keyof LHS, keyof RHS>```
   *
   * Which makes sense, given @see PropsNotShared
   */
  const keyofNotShared = {} as keyof typeof notShared;
  const keyofNotShared_firstSet = {} as Exclude<keyof RHS, keyof LHS>;
  const keyofNotShared_secondSet = {} as Exclude<keyof LHS, keyof RHS>;
  /**
   * Check that the above two types are indeed keys of notShared. Succeeds
   * because they are.
   */
  const checkKeyofNotShared_firstSet: keyof typeof notShared =
    {} as typeof keyofNotShared_firstSet;
  const checkKeyofNotShared_secondSet: keyof typeof notShared =
    {} as typeof keyofNotShared_secondSet;
  /**
   * Expected failures to illustrate that `keyofNotShared_firstSet` and
   * `keyofNotShared_secondSet` are not identical (not assignable to each
   * other).
   */
  const checkKeySetsNotSame_1: typeof keyofNotShared_firstSet =
    {} as typeof keyofNotShared_secondSet;
  const checkKeySetsNotSame_2: typeof keyofNotShared_secondSet =
    {} as typeof keyofNotShared_firstSet;
  /**
   * Some sets of values from notShared.
   */
  const notSharedVals_firstSet =
    {} as typeof notShared[typeof keyofNotShared_firstSet];
  const notSharedVals_secondSet =
    {} as typeof notShared[typeof keyofNotShared_secondSet];
  /**
   * Expected failures to show that `notSharedVals_firstSet` and
   * `notSharedVals_secondSet` are not identical.
   */
  const checkValSetsNotSame_1: typeof notSharedVals_firstSet =
    {} as typeof notSharedVals_secondSet;
  const checkValSetsNotSame_2: typeof notSharedVals_secondSet =
    {} as typeof notSharedVals_firstSet;

  /**
   * [====================================]
   * And now the main problems:
   * [====================================]
   */

  /**
   * (1)
   *
   * Should succeed but doesn't.
   *
   * Shouldn't a union of some values from notShared (rhs) be assignable to:
   * `typeof notShared[keyof typeof notShared]` (lhs) ?
   */
  const x1: typeof notShared[keyof typeof notShared] = {} as
    | typeof notSharedVals_firstSet
    | typeof notSharedVals_secondSet;
  /**
   * (2)
   *
   * (This is the issue referenced in title):
   *
   * Should fail, I would think.
   *
   * How is an interection of some of the values from an object (rhs)
   * assignable to a type which is any value of that object (lhs) ?
   *
   * (when the types of those values are not identical,
   * And they aren't according to above lines involving
   * `checkValSetsNotSame_1` and `checkValSetsNotSame_1`.)
   */
  const x2: typeof notShared[keyof typeof notShared] =
    {} as typeof notSharedVals_firstSet & typeof notSharedVals_secondSet;
  /**
   * (3)
   *
   * More weirdness:
   *
   * Should fail but doesn't.
   *
   * I would think, as doesn't it basically say A | B == A & B ?
   * (and A and B aren't identical)
   */
  const x3: typeof notSharedVals_firstSet | typeof notSharedVals_secondSet =
    {} as typeof notSharedVals_firstSet & typeof notSharedVals_secondSet;
}

πŸ™ Actual behavior

Given a generic object produced via an intersection, a union of it's values is not assignable to Obj[keyof Obj] (i.e. any value of it) (example 1 in comments), but an intersection is and shouldn't be (example 2 and in title).

πŸ™‚ Expected behavior

Assigning a union of it's values to Obj[keyof Obj] should succeed (example 1) and assigning an intersection of them should (I suppose) fail (example 2).

@iPherian
Copy link
Author

iPherian commented May 16, 2021

Hmm actually I may have made a mistake here. This helpful comment explained some things, and they make sense as to why doing T[K] is on the target side of a type relationship is equivalent to an intersection of all the value types of T.

It seems that this is caused by #30769.

We know keyof (A & B) is (keyof A) | (keyof B), and T[M | N] is T[M] | T[N], so T3[keyof T3] is resolved to T3[keyof T] | T3[keyof U] as expected, when it is in a "read position".

However, when T3[keyof T3] appears in the "write position", it uses an intersection instead of a union, so T3[keyof T3] is resolved to T3[keyof T] & T3[keyof U], which leads to the error.

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

1 participant