From b2af6051ef30de5d85763f200511da4eb86c840b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 3 Dec 2021 16:11:01 -0800 Subject: [PATCH] Improve isValidSpreadType check (#47010) * Fix getFalsyFlags for intersection types * Fix and simplify isValidSpreadType * Slight tweak * Add tests --- src/compiler/checker.ts | 14 +-- .../reference/spreadObjectOrFalsy.errors.txt | 42 +++++++++ .../reference/spreadObjectOrFalsy.js | 83 ++++++++++++++++++ .../reference/spreadObjectOrFalsy.symbols | 87 +++++++++++++++++++ .../reference/spreadObjectOrFalsy.types | 75 ++++++++++++++++ .../types/spread/spreadObjectOrFalsy.ts | 35 ++++++++ 6 files changed, 326 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/spreadObjectOrFalsy.errors.txt create mode 100644 tests/baselines/reference/spreadObjectOrFalsy.js create mode 100644 tests/baselines/reference/spreadObjectOrFalsy.symbols create mode 100644 tests/baselines/reference/spreadObjectOrFalsy.types create mode 100644 tests/cases/conformance/types/spread/spreadObjectOrFalsy.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 16d8982b126fd..25afc98fc4eb2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20871,7 +20871,7 @@ namespace ts { // flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns // no flags for all other types (including non-falsy literal types). function getFalsyFlags(type: Type): TypeFlags { - return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((type as UnionType).types) : + return type.flags & TypeFlags.UnionOrIntersection ? getFalsyFlagsOfTypes((type as UnionType).types) : type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value === "" ? TypeFlags.StringLiteral : 0 : type.flags & TypeFlags.NumberLiteral ? (type as NumberLiteralType).value === 0 ? TypeFlags.NumberLiteral : 0 : type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt(type as BigIntLiteralType) ? TypeFlags.BigIntLiteral : 0 : @@ -27284,15 +27284,9 @@ namespace ts { } function isValidSpreadType(type: Type): boolean { - if (type.flags & TypeFlags.Instantiable) { - const constraint = getBaseConstraintOfType(type); - if (constraint !== undefined) { - return isValidSpreadType(constraint); - } - } - return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || - getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) || - type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isValidSpreadType)); + const t = removeDefinitelyFalsyTypes(mapType(type, getBaseConstraintOrType)); + return !!(t.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || + t.flags & TypeFlags.UnionOrIntersection && every((t as UnionOrIntersectionType).types, isValidSpreadType)); } function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { diff --git a/tests/baselines/reference/spreadObjectOrFalsy.errors.txt b/tests/baselines/reference/spreadObjectOrFalsy.errors.txt new file mode 100644 index 0000000000000..02c3a9aaf0cd5 --- /dev/null +++ b/tests/baselines/reference/spreadObjectOrFalsy.errors.txt @@ -0,0 +1,42 @@ +tests/cases/conformance/types/spread/spreadObjectOrFalsy.ts(2,14): error TS2698: Spread types may only be created from object types. +tests/cases/conformance/types/spread/spreadObjectOrFalsy.ts(10,14): error TS2698: Spread types may only be created from object types. + + +==== tests/cases/conformance/types/spread/spreadObjectOrFalsy.ts (2 errors) ==== + function f1(a: T & undefined) { + return { ...a }; // Error + ~~~~ +!!! error TS2698: Spread types may only be created from object types. + } + + function f2(a: T | T & undefined) { + return { ...a }; + } + + function f3(a: T) { + return { ...a }; // Error + ~~~~ +!!! error TS2698: Spread types may only be created from object types. + } + + function f4(a: object | T) { + return { ...a }; + } + + function f5(a: S | T) { + return { ...a }; + } + + function f6(a: T) { + return { ...a }; + } + + // Repro from #46976 + + function g1(a: A) { + const { z } = a; + return { + ...z + }; + } + \ No newline at end of file diff --git a/tests/baselines/reference/spreadObjectOrFalsy.js b/tests/baselines/reference/spreadObjectOrFalsy.js new file mode 100644 index 0000000000000..b53d4ca23c97d --- /dev/null +++ b/tests/baselines/reference/spreadObjectOrFalsy.js @@ -0,0 +1,83 @@ +//// [spreadObjectOrFalsy.ts] +function f1(a: T & undefined) { + return { ...a }; // Error +} + +function f2(a: T | T & undefined) { + return { ...a }; +} + +function f3(a: T) { + return { ...a }; // Error +} + +function f4(a: object | T) { + return { ...a }; +} + +function f5(a: S | T) { + return { ...a }; +} + +function f6(a: T) { + return { ...a }; +} + +// Repro from #46976 + +function g1(a: A) { + const { z } = a; + return { + ...z + }; +} + + +//// [spreadObjectOrFalsy.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 f1(a) { + return __assign({}, a); // Error +} +function f2(a) { + return __assign({}, a); +} +function f3(a) { + return __assign({}, a); // Error +} +function f4(a) { + return __assign({}, a); +} +function f5(a) { + return __assign({}, a); +} +function f6(a) { + return __assign({}, a); +} +// Repro from #46976 +function g1(a) { + var z = a.z; + return __assign({}, z); +} + + +//// [spreadObjectOrFalsy.d.ts] +declare function f1(a: T & undefined): any; +declare function f2(a: T | T & undefined): T | (T & undefined); +declare function f3(a: T): any; +declare function f4(a: object | T): {}; +declare function f5(a: S | T): S | T; +declare function f6(a: T): T; +declare function g1(a: A): (T | undefined) & T; diff --git a/tests/baselines/reference/spreadObjectOrFalsy.symbols b/tests/baselines/reference/spreadObjectOrFalsy.symbols new file mode 100644 index 0000000000000..f89f5387fa955 --- /dev/null +++ b/tests/baselines/reference/spreadObjectOrFalsy.symbols @@ -0,0 +1,87 @@ +=== tests/cases/conformance/types/spread/spreadObjectOrFalsy.ts === +function f1(a: T & undefined) { +>f1 : Symbol(f1, Decl(spreadObjectOrFalsy.ts, 0, 0)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 0, 12)) +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 0, 15)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 0, 12)) + + return { ...a }; // Error +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 0, 15)) +} + +function f2(a: T | T & undefined) { +>f2 : Symbol(f2, Decl(spreadObjectOrFalsy.ts, 2, 1)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 4, 12)) +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 4, 15)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 4, 12)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 4, 12)) + + return { ...a }; +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 4, 15)) +} + +function f3(a: T) { +>f3 : Symbol(f3, Decl(spreadObjectOrFalsy.ts, 6, 1)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 8, 12)) +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 8, 33)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 8, 12)) + + return { ...a }; // Error +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 8, 33)) +} + +function f4(a: object | T) { +>f4 : Symbol(f4, Decl(spreadObjectOrFalsy.ts, 10, 1)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 12, 12)) +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 12, 33)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 12, 12)) + + return { ...a }; +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 12, 33)) +} + +function f5(a: S | T) { +>f5 : Symbol(f5, Decl(spreadObjectOrFalsy.ts, 14, 1)) +>S : Symbol(S, Decl(spreadObjectOrFalsy.ts, 16, 12)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 16, 14)) +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 16, 36)) +>S : Symbol(S, Decl(spreadObjectOrFalsy.ts, 16, 12)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 16, 14)) + + return { ...a }; +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 16, 36)) +} + +function f6(a: T) { +>f6 : Symbol(f6, Decl(spreadObjectOrFalsy.ts, 18, 1)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 20, 12)) +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 20, 42)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 20, 12)) + + return { ...a }; +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 20, 42)) +} + +// Repro from #46976 + +function g1(a: A) { +>g1 : Symbol(g1, Decl(spreadObjectOrFalsy.ts, 22, 1)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 26, 12)) +>A : Symbol(A, Decl(spreadObjectOrFalsy.ts, 26, 25)) +>z : Symbol(z, Decl(spreadObjectOrFalsy.ts, 26, 37)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 26, 12)) +>T : Symbol(T, Decl(spreadObjectOrFalsy.ts, 26, 12)) +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 26, 64)) +>A : Symbol(A, Decl(spreadObjectOrFalsy.ts, 26, 25)) + + const { z } = a; +>z : Symbol(z, Decl(spreadObjectOrFalsy.ts, 27, 11)) +>a : Symbol(a, Decl(spreadObjectOrFalsy.ts, 26, 64)) + + return { + ...z +>z : Symbol(z, Decl(spreadObjectOrFalsy.ts, 27, 11)) + + }; +} + diff --git a/tests/baselines/reference/spreadObjectOrFalsy.types b/tests/baselines/reference/spreadObjectOrFalsy.types new file mode 100644 index 0000000000000..279b7cf7b66bc --- /dev/null +++ b/tests/baselines/reference/spreadObjectOrFalsy.types @@ -0,0 +1,75 @@ +=== tests/cases/conformance/types/spread/spreadObjectOrFalsy.ts === +function f1(a: T & undefined) { +>f1 : (a: T & undefined) => any +>a : T & undefined + + return { ...a }; // Error +>{ ...a } : any +>a : T & undefined +} + +function f2(a: T | T & undefined) { +>f2 : (a: T | (T & undefined)) => T | (T & undefined) +>a : T | (T & undefined) + + return { ...a }; +>{ ...a } : T | (T & undefined) +>a : T | (T & undefined) +} + +function f3(a: T) { +>f3 : (a: T) => any +>a : T + + return { ...a }; // Error +>{ ...a } : any +>a : T +} + +function f4(a: object | T) { +>f4 : (a: object | T) => {} +>a : object | T + + return { ...a }; +>{ ...a } : {} +>a : object | T +} + +function f5(a: S | T) { +>f5 : (a: S | T) => S | T +>a : S | T + + return { ...a }; +>{ ...a } : S | T +>a : S | T +} + +function f6(a: T) { +>f6 : (a: T) => T +>a : T + + return { ...a }; +>{ ...a } : T +>a : T +} + +// Repro from #46976 + +function g1(a: A) { +>g1 : (a: A) => (T | undefined) & T +>z : (T | undefined) & T +>a : A + + const { z } = a; +>z : (T | undefined) & T +>a : A + + return { +>{ ...z } : (T | undefined) & T + + ...z +>z : (T | undefined) & T + + }; +} + diff --git a/tests/cases/conformance/types/spread/spreadObjectOrFalsy.ts b/tests/cases/conformance/types/spread/spreadObjectOrFalsy.ts new file mode 100644 index 0000000000000..86d7357842eac --- /dev/null +++ b/tests/cases/conformance/types/spread/spreadObjectOrFalsy.ts @@ -0,0 +1,35 @@ +// @strict: true +// @declaration: true + +function f1(a: T & undefined) { + return { ...a }; // Error +} + +function f2(a: T | T & undefined) { + return { ...a }; +} + +function f3(a: T) { + return { ...a }; // Error +} + +function f4(a: object | T) { + return { ...a }; +} + +function f5(a: S | T) { + return { ...a }; +} + +function f6(a: T) { + return { ...a }; +} + +// Repro from #46976 + +function g1(a: A) { + const { z } = a; + return { + ...z + }; +}