Generic spread expressions in object literals

In TypeScript 3.2, object literals now allow generic spread expressions which now produce intersection types, similar to the Object.assign function and JSX literals. For example:

  1. function taggedObject<T, U extends string>(obj: T, tag: U) {
  2. return { ...obj, tag }; // T & { tag: U }
  3. }
  4. let x = taggedObject({ x: 10, y: 20 }, "point"); // { x: number, y: number } & { tag: "point" }

Property assignments and non-generic spread expressions are merged to the greatest extent possible on either side of a generic spread expression. For example:

  1. function foo1<T>(t: T, obj1: { a: string }, obj2: { b: string }) {
  2. return { ...obj1, x: 1, ...t, ...obj2, y: 2 }; // { a: string, x: number } & T & { b: string, y: number }
  3. }

Non-generic spread expressions continue to be processed as before: Call and construct signatures are stripped, only non-method properties are preserved, and for properties with the same name, the type of the rightmost property is used. This contrasts with intersection types which concatenate call and construct signatures, preserve all properties, and intersect the types of properties with the same name. Thus, spreads of the same types may produce different results when they are created through instantiation of generic types:

  1. function spread<T, U>(t: T, u: U) {
  2. return { ...t, ...u }; // T & U
  3. }
  4. declare let x: { a: string, b: number };
  5. declare let y: { b: string, c: boolean };
  6. let s1 = { ...x, ...y }; // { a: string, b: string, c: boolean }
  7. let s2 = spread(x, y); // { a: string, b: number } & { b: string, c: boolean }
  8. let b1 = s1.b; // string
  9. let b2 = s2.b; // number & string