Skip to content

Commit

Permalink
Do for unions of many empty-object-spreadables what we did for unions…
Browse files Browse the repository at this point in the history
… of 2 (#42233)

* Do for unions of many empty-object-spreadables what we did for unions of 2

* Accept baseline
  • Loading branch information
weswigham authored Jan 7, 2021
1 parent dbba8b3 commit 11606e4
Show file tree
Hide file tree
Showing 15 changed files with 281 additions and 58 deletions.
25 changes: 12 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14670,19 +14670,18 @@ namespace ts {
}

function tryMergeUnionOfObjectTypeAndEmptyObject(type: UnionType, readonly: boolean): Type | undefined {
if (type.types.length === 2) {
const firstType = type.types[0];
const secondType = type.types[1];
if (every(type.types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) {
return isEmptyObjectType(firstType) ? firstType : isEmptyObjectType(secondType) ? secondType : emptyObjectType;
}
if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(firstType)) {
return getAnonymousPartialType(secondType);
}
if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(secondType)) {
return getAnonymousPartialType(firstType);
}
if (every(type.types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) {
return find(type.types, isEmptyObjectType) || emptyObjectType;
}
const firstType = find(type.types, t => !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t));
if (!firstType) {
return undefined;
}
const secondType = firstType && find(type.types, t => t !== firstType && !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t));
if (secondType) {
return undefined;
}
return getAnonymousPartialType(firstType);

function getAnonymousPartialType(type: Type) {
// gets the type as if it had been spread, but where everything in the spread is made optional
Expand All @@ -14695,7 +14694,7 @@ namespace ts {
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
const flags = SymbolFlags.Property | SymbolFlags.Optional;
const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0));
result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop);
result.type = isSetonlyAccessor ? undefinedType : getUnionType([getTypeOfSymbol(prop), undefinedType]);
result.declarations = prop.declarations;
result.nameType = getSymbolLinks(prop).nameType;
result.syntheticOrigin = prop;
Expand Down
12 changes: 6 additions & 6 deletions tests/baselines/reference/objectSpread.types
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ function conditionalSpreadBoolean(b: boolean) : { x: number, y: number } {
>14 : 14
}
let o2 = { ...b && { x: 21 }}
>o2 : { x?: number; }
>{ ...b && { x: 21 }} : { x?: number; }
>o2 : { x?: number | undefined; }
>{ ...b && { x: 21 }} : { x?: number | undefined; }
>b && { x: 21 } : false | { x: number; }
>b : boolean
>{ x: 21 } : { x: number; }
Expand Down Expand Up @@ -270,8 +270,8 @@ function conditionalSpreadNumber(nt: number): { x: number, y: number } {
>nt : number
}
let o2 = { ...nt && { x: nt }}
>o2 : { x?: number; }
>{ ...nt && { x: nt }} : { x?: number; }
>o2 : { x?: number | undefined; }
>{ ...nt && { x: nt }} : { x?: number | undefined; }
>nt && { x: nt } : 0 | { x: number; }
>nt : number
>{ x: nt } : { x: number; }
Expand Down Expand Up @@ -311,8 +311,8 @@ function conditionalSpreadString(st: string): { x: string, y: number } {
>st : string
}
let o2 = { ...st && { x: st }}
>o2 : { x?: string; }
>{ ...st && { x: st }} : { x?: string; }
>o2 : { x?: string | undefined; }
>{ ...st && { x: st }} : { x?: string | undefined; }
>st && { x: st } : "" | { x: string; }
>st : string
>{ x: st } : { x: string; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
=== tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts ===
function f(cnd: Record<number, boolean>){
>f : (cnd: Record<number, boolean>) => { prop20a?: number; prop20b?: number; prop19a?: number; prop19b?: number; prop18a?: number; prop18b?: number; prop17a?: number; prop17b?: number; prop16a?: number; prop16b?: number; prop15a?: number; prop15b?: number; prop14a?: number; prop14b?: number; prop13a?: number; prop13b?: number; prop12a?: number; prop12b?: number; prop11a?: number; prop11b?: number; prop10a?: number; prop10b?: number; prop9a?: number; prop9b?: number; prop8a?: number; prop8b?: number; prop7a?: number; prop7b?: number; prop6a?: number; prop6b?: number; prop5a?: number; prop5b?: number; prop4a?: number; prop4b?: number; prop3a?: number; prop3b?: number; prop0?: number; }
>f : (cnd: Record<number, boolean>) => { prop20a?: number | undefined; prop20b?: number | undefined; prop19a?: number | undefined; prop19b?: number | undefined; prop18a?: number | undefined; prop18b?: number | undefined; prop17a?: number | undefined; prop17b?: number | undefined; prop16a?: number | undefined; prop16b?: number | undefined; prop15a?: number | undefined; prop15b?: number | undefined; prop14a?: number | undefined; prop14b?: number | undefined; prop13a?: number | undefined; prop13b?: number | undefined; prop12a?: number | undefined; prop12b?: number | undefined; prop11a?: number | undefined; prop11b?: number | undefined; prop10a?: number | undefined; prop10b?: number | undefined; prop9a?: number | undefined; prop9b?: number | undefined; prop8a?: number | undefined; prop8b?: number | undefined; prop7a?: number | undefined; prop7b?: number | undefined; prop6a?: number | undefined; prop6b?: number | undefined; prop5a?: number | undefined; prop5b?: number | undefined; prop4a?: number | undefined; prop4b?: number | undefined; prop3a?: number | undefined; prop3b?: number | undefined; prop0?: number | undefined; }
>cnd : Record<number, boolean>

// Type is a union of 2^(n-1) members, where n is the number of spread objects
return {
>{ // Without this one, it collapses to {} ? ...(cnd[1] && cnd[2] && { prop0: 0, }), // With one prop each, it collapses to a single object (#34853?) ...(cnd[3] && { prop3a: 1, prop3b: 1, }), ...(cnd[4] && { prop4a: 1, prop4b: 1, }), ...(cnd[5] && { prop5a: 1, prop5b: 1, }), ...(cnd[6] && { prop6a: 1, prop6b: 1, }), ...(cnd[7] && { prop7a: 1, prop7b: 1, }), ...(cnd[8] && { prop8a: 1, prop8b: 1, }), ...(cnd[9] && { prop9a: 1, prop9b: 1, }), ...(cnd[10] && { prop10a: 1, prop10b: 1, }), ...(cnd[11] && { prop11a: 1, prop11b: 1, }), ...(cnd[12] && { prop12a: 1, prop12b: 1, }), ...(cnd[13] && { prop13a: 1, prop13b: 1, }), ...(cnd[14] && { prop14a: 1, prop14b: 1, }), ...(cnd[15] && { prop15a: 1, prop15b: 1, }), ...(cnd[16] && { prop16a: 1, prop16b: 1, }), ...(cnd[17] && { prop17a: 1, prop17b: 1, }), ...(cnd[18] && { prop18a: 1, prop18b: 1, }), ...(cnd[19] && { prop19a: 1, prop19b: 1, }), ...(cnd[20] && { prop20a: 1, prop20b: 1, }), } : { prop20a?: number; prop20b?: number; prop19a?: number; prop19b?: number; prop18a?: number; prop18b?: number; prop17a?: number; prop17b?: number; prop16a?: number; prop16b?: number; prop15a?: number; prop15b?: number; prop14a?: number; prop14b?: number; prop13a?: number; prop13b?: number; prop12a?: number; prop12b?: number; prop11a?: number; prop11b?: number; prop10a?: number; prop10b?: number; prop9a?: number; prop9b?: number; prop8a?: number; prop8b?: number; prop7a?: number; prop7b?: number; prop6a?: number; prop6b?: number; prop5a?: number; prop5b?: number; prop4a?: number; prop4b?: number; prop3a?: number; prop3b?: number; prop0?: number; }
>{ // Without this one, it collapses to {} ? ...(cnd[1] && cnd[2] && { prop0: 0, }), // With one prop each, it collapses to a single object (#34853?) ...(cnd[3] && { prop3a: 1, prop3b: 1, }), ...(cnd[4] && { prop4a: 1, prop4b: 1, }), ...(cnd[5] && { prop5a: 1, prop5b: 1, }), ...(cnd[6] && { prop6a: 1, prop6b: 1, }), ...(cnd[7] && { prop7a: 1, prop7b: 1, }), ...(cnd[8] && { prop8a: 1, prop8b: 1, }), ...(cnd[9] && { prop9a: 1, prop9b: 1, }), ...(cnd[10] && { prop10a: 1, prop10b: 1, }), ...(cnd[11] && { prop11a: 1, prop11b: 1, }), ...(cnd[12] && { prop12a: 1, prop12b: 1, }), ...(cnd[13] && { prop13a: 1, prop13b: 1, }), ...(cnd[14] && { prop14a: 1, prop14b: 1, }), ...(cnd[15] && { prop15a: 1, prop15b: 1, }), ...(cnd[16] && { prop16a: 1, prop16b: 1, }), ...(cnd[17] && { prop17a: 1, prop17b: 1, }), ...(cnd[18] && { prop18a: 1, prop18b: 1, }), ...(cnd[19] && { prop19a: 1, prop19b: 1, }), ...(cnd[20] && { prop20a: 1, prop20b: 1, }), } : { prop20a?: number | undefined; prop20b?: number | undefined; prop19a?: number | undefined; prop19b?: number | undefined; prop18a?: number | undefined; prop18b?: number | undefined; prop17a?: number | undefined; prop17b?: number | undefined; prop16a?: number | undefined; prop16b?: number | undefined; prop15a?: number | undefined; prop15b?: number | undefined; prop14a?: number | undefined; prop14b?: number | undefined; prop13a?: number | undefined; prop13b?: number | undefined; prop12a?: number | undefined; prop12b?: number | undefined; prop11a?: number | undefined; prop11b?: number | undefined; prop10a?: number | undefined; prop10b?: number | undefined; prop9a?: number | undefined; prop9b?: number | undefined; prop8a?: number | undefined; prop8b?: number | undefined; prop7a?: number | undefined; prop7b?: number | undefined; prop6a?: number | undefined; prop6b?: number | undefined; prop5a?: number | undefined; prop5b?: number | undefined; prop4a?: number | undefined; prop4b?: number | undefined; prop3a?: number | undefined; prop3b?: number | undefined; prop0?: number | undefined; }

// Without this one, it collapses to {} ?
...(cnd[1] &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function parseWithSpread(config: Record<string, number>): Props {
>config : Record<string, number>

return {
>{ ...config.a !== undefined && { a: config.a.toString() }, ...config.b !== undefined && { b: config.b.toString() }, ...config.c !== undefined && { c: config.c.toString() }, ...config.d !== undefined && { d: config.d.toString() }, ...config.e !== undefined && { e: config.e.toString() }, ...config.f !== undefined && { f: config.f.toString() }, ...config.g !== undefined && { g: config.g.toString() }, ...config.h !== undefined && { h: config.h.toString() }, ...config.i !== undefined && { i: config.i.toString() }, ...config.j !== undefined && { j: config.j.toString() }, ...config.k !== undefined && { k: config.k.toString() }, ...config.l !== undefined && { l: config.l.toString() }, ...config.m !== undefined && { m: config.m.toString() }, ...config.n !== undefined && { n: config.n.toString() }, ...config.o !== undefined && { o: config.o.toString() }, ...config.p !== undefined && { p: config.p.toString() }, ...config.q !== undefined && { q: config.q.toString() }, ...config.r !== undefined && { r: config.r.toString() }, ...config.s !== undefined && { s: config.s.toString() }, ...config.t !== undefined && { t: config.t.toString() }, ...config.u !== undefined && { u: config.u.toString() }, ...config.v !== undefined && { v: config.v.toString() }, ...config.w !== undefined && { w: config.w.toString() }, ...config.x !== undefined && { x: config.x.toString() }, ...config.y !== undefined && { y: config.y.toString() }, ...config.z !== undefined && { z: config.z.toString() } } : { z?: string; y?: string; x?: string; w?: string; v?: string; u?: string; t?: string; s?: string; r?: string; q?: string; p?: string; o?: string; n?: string; m?: string; l?: string; k?: string; j?: string; i?: string; h?: string; g?: string; f?: string; e?: string; d?: string; c?: string; b?: string; a?: string; }
>{ ...config.a !== undefined && { a: config.a.toString() }, ...config.b !== undefined && { b: config.b.toString() }, ...config.c !== undefined && { c: config.c.toString() }, ...config.d !== undefined && { d: config.d.toString() }, ...config.e !== undefined && { e: config.e.toString() }, ...config.f !== undefined && { f: config.f.toString() }, ...config.g !== undefined && { g: config.g.toString() }, ...config.h !== undefined && { h: config.h.toString() }, ...config.i !== undefined && { i: config.i.toString() }, ...config.j !== undefined && { j: config.j.toString() }, ...config.k !== undefined && { k: config.k.toString() }, ...config.l !== undefined && { l: config.l.toString() }, ...config.m !== undefined && { m: config.m.toString() }, ...config.n !== undefined && { n: config.n.toString() }, ...config.o !== undefined && { o: config.o.toString() }, ...config.p !== undefined && { p: config.p.toString() }, ...config.q !== undefined && { q: config.q.toString() }, ...config.r !== undefined && { r: config.r.toString() }, ...config.s !== undefined && { s: config.s.toString() }, ...config.t !== undefined && { t: config.t.toString() }, ...config.u !== undefined && { u: config.u.toString() }, ...config.v !== undefined && { v: config.v.toString() }, ...config.w !== undefined && { w: config.w.toString() }, ...config.x !== undefined && { x: config.x.toString() }, ...config.y !== undefined && { y: config.y.toString() }, ...config.z !== undefined && { z: config.z.toString() } } : { z?: string | undefined; y?: string | undefined; x?: string | undefined; w?: string | undefined; v?: string | undefined; u?: string | undefined; t?: string | undefined; s?: string | undefined; r?: string | undefined; q?: string | undefined; p?: string | undefined; o?: string | undefined; n?: string | undefined; m?: string | undefined; l?: string | undefined; k?: string | undefined; j?: string | undefined; i?: string | undefined; h?: string | undefined; g?: string | undefined; f?: string | undefined; e?: string | undefined; d?: string | undefined; c?: string | undefined; b?: string | undefined; a?: string | undefined; }

...config.a !== undefined && { a: config.a.toString() },
>config.a !== undefined && { a: config.a.toString() } : false | { a: string; }
Expand Down
48 changes: 48 additions & 0 deletions tests/baselines/reference/spreadIdenticalTypesRemoved.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//// [spreadIdenticalTypesRemoved.ts]
interface Animal {
name: string;
kind: string;
age: number;
location: string;
owner: object;
}

function clonePet(pet: Animal, fullCopy?: boolean) {
return {
name: pet.name,
kind: pet.kind,
...(fullCopy && pet),
}
}

interface Animal2 {
name: string;
owner?: string;
}
function billOwner(pet: Animal2) {
return {
...(pet.owner && pet),
paid: false
}
}


//// [spreadIdenticalTypesRemoved.js]
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function clonePet(pet, fullCopy) {
return __assign({ name: pet.name, kind: pet.kind }, (fullCopy && pet));
}
function billOwner(pet) {
return __assign(__assign({}, (pet.owner && pet)), { paid: false });
}
71 changes: 71 additions & 0 deletions tests/baselines/reference/spreadIdenticalTypesRemoved.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
=== tests/cases/compiler/spreadIdenticalTypesRemoved.ts ===
interface Animal {
>Animal : Symbol(Animal, Decl(spreadIdenticalTypesRemoved.ts, 0, 0))

name: string;
>name : Symbol(Animal.name, Decl(spreadIdenticalTypesRemoved.ts, 0, 18))

kind: string;
>kind : Symbol(Animal.kind, Decl(spreadIdenticalTypesRemoved.ts, 1, 17))

age: number;
>age : Symbol(Animal.age, Decl(spreadIdenticalTypesRemoved.ts, 2, 17))

location: string;
>location : Symbol(Animal.location, Decl(spreadIdenticalTypesRemoved.ts, 3, 16))

owner: object;
>owner : Symbol(Animal.owner, Decl(spreadIdenticalTypesRemoved.ts, 4, 21))
}

function clonePet(pet: Animal, fullCopy?: boolean) {
>clonePet : Symbol(clonePet, Decl(spreadIdenticalTypesRemoved.ts, 6, 1))
>pet : Symbol(pet, Decl(spreadIdenticalTypesRemoved.ts, 8, 18))
>Animal : Symbol(Animal, Decl(spreadIdenticalTypesRemoved.ts, 0, 0))
>fullCopy : Symbol(fullCopy, Decl(spreadIdenticalTypesRemoved.ts, 8, 30))

return {
name: pet.name,
>name : Symbol(name, Decl(spreadIdenticalTypesRemoved.ts, 9, 12))
>pet.name : Symbol(Animal.name, Decl(spreadIdenticalTypesRemoved.ts, 0, 18))
>pet : Symbol(pet, Decl(spreadIdenticalTypesRemoved.ts, 8, 18))
>name : Symbol(Animal.name, Decl(spreadIdenticalTypesRemoved.ts, 0, 18))

kind: pet.kind,
>kind : Symbol(kind, Decl(spreadIdenticalTypesRemoved.ts, 10, 23))
>pet.kind : Symbol(Animal.kind, Decl(spreadIdenticalTypesRemoved.ts, 1, 17))
>pet : Symbol(pet, Decl(spreadIdenticalTypesRemoved.ts, 8, 18))
>kind : Symbol(Animal.kind, Decl(spreadIdenticalTypesRemoved.ts, 1, 17))

...(fullCopy && pet),
>fullCopy : Symbol(fullCopy, Decl(spreadIdenticalTypesRemoved.ts, 8, 30))
>pet : Symbol(pet, Decl(spreadIdenticalTypesRemoved.ts, 8, 18))
}
}

interface Animal2 {
>Animal2 : Symbol(Animal2, Decl(spreadIdenticalTypesRemoved.ts, 14, 1))

name: string;
>name : Symbol(Animal2.name, Decl(spreadIdenticalTypesRemoved.ts, 16, 19))

owner?: string;
>owner : Symbol(Animal2.owner, Decl(spreadIdenticalTypesRemoved.ts, 17, 17))
}
function billOwner(pet: Animal2) {
>billOwner : Symbol(billOwner, Decl(spreadIdenticalTypesRemoved.ts, 19, 1))
>pet : Symbol(pet, Decl(spreadIdenticalTypesRemoved.ts, 20, 19))
>Animal2 : Symbol(Animal2, Decl(spreadIdenticalTypesRemoved.ts, 14, 1))

return {
...(pet.owner && pet),
>pet.owner : Symbol(Animal2.owner, Decl(spreadIdenticalTypesRemoved.ts, 17, 17))
>pet : Symbol(pet, Decl(spreadIdenticalTypesRemoved.ts, 20, 19))
>owner : Symbol(Animal2.owner, Decl(spreadIdenticalTypesRemoved.ts, 17, 17))
>pet : Symbol(pet, Decl(spreadIdenticalTypesRemoved.ts, 20, 19))

paid: false
>paid : Symbol(paid, Decl(spreadIdenticalTypesRemoved.ts, 22, 30))
}
}

Loading

0 comments on commit 11606e4

Please sign in to comment.