From 52fd5005e494ff9464fe181332b4a3fb730a22eb Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 9 Oct 2025 09:03:23 -0700 Subject: [PATCH 1/4] Use Partial instead of Omit when rest spreading Implements suggestion from #62572 --- src/compiler/checker.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 48bc0da113816..f2af9fb70e594 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2292,6 +2292,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var deferredGlobalAsyncDisposableType: ObjectType | undefined; var deferredGlobalExtractSymbol: Symbol | undefined; var deferredGlobalOmitSymbol: Symbol | undefined; + var deferredGlobalPartialSymbol: Symbol | undefined; var deferredGlobalAwaitedSymbol: Symbol | undefined; var deferredGlobalBigIntType: ObjectType | undefined; var deferredGlobalNaNSymbol: Symbol | undefined; @@ -11577,6 +11578,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return source; } + // In the case where we have + // const { [key]: _, ...rest } = obj; + // where key is keyof typeof obj, then the best type for rest is Partial + if (isOrContainsKeyofT(omitKeyType, source)) { + const partialTypeAlias = getGlobalPartialSymbol(); + if (!partialTypeAlias) { + return errorType; + } + return getTypeAliasInstantiation(partialTypeAlias, [source]); + } + const omitTypeAlias = getGlobalOmitSymbol(); if (!omitTypeAlias) { return errorType; @@ -11592,6 +11604,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } + function isOrContainsKeyofT(keyType: Type, sourceType: Type): boolean { + if (keyType.flags & TypeFlags.Union) { + return !!forEach((keyType as UnionType).types, t => isOrContainsKeyofT(t, sourceType)); + } + if (keyType.flags & TypeFlags.Index && (keyType as IndexType).type === sourceType) { + return true; + } + return false; + } + function isGenericTypeWithUndefinedConstraint(type: Type) { return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); } @@ -17573,6 +17595,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return deferredGlobalOmitSymbol === unknownSymbol ? undefined : deferredGlobalOmitSymbol; } + function getGlobalPartialSymbol(): Symbol | undefined { + // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times + deferredGlobalPartialSymbol ||= getGlobalTypeAliasSymbol("Partial" as __String, /*arity*/ 1, /*reportErrors*/ true) || unknownSymbol; + return deferredGlobalPartialSymbol === unknownSymbol ? undefined : deferredGlobalPartialSymbol; + } + function getGlobalAwaitedSymbol(reportErrors: boolean): Symbol | undefined { // Only cache `unknownSymbol` if we are reporting errors so that we don't report the error more than once. deferredGlobalAwaitedSymbol ||= getGlobalTypeAliasSymbol("Awaited" as __String, /*arity*/ 1, reportErrors) || (reportErrors ? unknownSymbol : undefined); From b1bd4cd9ae2477e1fb435907b704052c470acd76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 19:37:12 +0000 Subject: [PATCH 2/4] Initial plan From 03d7fa19073c5c9cf3a6d1e4339cae9747d09fbb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 19:48:41 +0000 Subject: [PATCH 3/4] Add compiler test for Partial rest spreading with keyof T Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- .../reference/restSpreadingWithKeyofT.js | 139 ++++++++++ .../reference/restSpreadingWithKeyofT.symbols | 188 +++++++++++++ .../reference/restSpreadingWithKeyofT.types | 251 ++++++++++++++++++ .../cases/compiler/restSpreadingWithKeyofT.ts | 57 ++++ 4 files changed, 635 insertions(+) create mode 100644 tests/baselines/reference/restSpreadingWithKeyofT.js create mode 100644 tests/baselines/reference/restSpreadingWithKeyofT.symbols create mode 100644 tests/baselines/reference/restSpreadingWithKeyofT.types create mode 100644 tests/cases/compiler/restSpreadingWithKeyofT.ts diff --git a/tests/baselines/reference/restSpreadingWithKeyofT.js b/tests/baselines/reference/restSpreadingWithKeyofT.js new file mode 100644 index 0000000000000..a0a47b07b4111 --- /dev/null +++ b/tests/baselines/reference/restSpreadingWithKeyofT.js @@ -0,0 +1,139 @@ +//// [tests/cases/compiler/restSpreadingWithKeyofT.ts] //// + +//// [restSpreadingWithKeyofT.ts] +// Test case 1: Using keyof T directly in rest spreading +// Should result in Partial instead of Omit +function f1(obj: T, key: keyof T) { + const { [key]: removed, ...rest } = obj; + return rest; +} + +// Test case 2: Union of keyof T +// Should result in Partial since both k1 and k2 are keyof T +function f2(obj: T, k1: keyof T, k2: keyof T) { + const { [k1]: removed1, [k2]: removed2, ...rest } = obj; + return rest; +} + +// Test case 3: keyof T with additional literal +// Should still use Partial since the union contains keyof T +function f3(obj: T, key: keyof T | "extra") { + const { [key]: removed, ...rest } = obj; + return rest; +} + +// Test case 4: Specific type with keyof +type MyObj = { a: string; b: number; c: boolean; }; +function f4(obj: MyObj, key: keyof MyObj) { + const { [key]: removed, ...rest } = obj; + return rest; +} + +// Test case 5: Constraint with keyof +function f5(obj: T, key: keyof T) { + const { [key]: removed, ...rest } = obj; + return rest; +} + +// Test case 6: Multiple parameters with keyof in object literal +function f6(obj: T, k1: keyof T, k2: keyof T, k3: keyof T) { + const { [k1]: r1, [k2]: r2, [k3]: r3, ...rest } = obj; + return rest; +} + +// Test case 7: Nested rest with keyof +function f7(obj: T, key: keyof T) { + const { [key]: val, ...rest } = obj; + const consumed = val; + return { consumed, rest }; +} + +// Test case 8: Array of keyof (not applicable but shows edge case) +function f8(obj: T, keys: Array) { + // Can't destructure with array, but showing the type relationship + const key = keys[0]; + const { [key]: removed, ...rest } = obj; + return rest; +} + + +//// [restSpreadingWithKeyofT.js] +"use strict"; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +// Test case 1: Using keyof T directly in rest spreading +// Should result in Partial instead of Omit +function f1(obj, key) { + var _a = obj, _b = key, removed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); + return rest; +} +// Test case 2: Union of keyof T +// Should result in Partial since both k1 and k2 are keyof T +function f2(obj, k1, k2) { + var _a = obj, _b = k1, removed1 = _a[_b], _c = k2, removed2 = _a[_c], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + "", typeof _c === "symbol" ? _c : _c + ""]); + return rest; +} +// Test case 3: keyof T with additional literal +// Should still use Partial since the union contains keyof T +function f3(obj, key) { + var _a = obj, _b = key, removed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); + return rest; +} +function f4(obj, key) { + var _a = obj, _b = key, removed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); + return rest; +} +// Test case 5: Constraint with keyof +function f5(obj, key) { + var _a = obj, _b = key, removed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); + return rest; +} +// Test case 6: Multiple parameters with keyof in object literal +function f6(obj, k1, k2, k3) { + var _a = obj, _b = k1, r1 = _a[_b], _c = k2, r2 = _a[_c], _d = k3, r3 = _a[_d], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + "", typeof _c === "symbol" ? _c : _c + "", typeof _d === "symbol" ? _d : _d + ""]); + return rest; +} +// Test case 7: Nested rest with keyof +function f7(obj, key) { + var _a = obj, _b = key, val = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); + var consumed = val; + return { consumed: consumed, rest: rest }; +} +// Test case 8: Array of keyof (not applicable but shows edge case) +function f8(obj, keys) { + // Can't destructure with array, but showing the type relationship + var key = keys[0]; + var _a = obj, _b = key, removed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); + return rest; +} + + +//// [restSpreadingWithKeyofT.d.ts] +declare function f1(obj: T, key: keyof T): Partial; +declare function f2(obj: T, k1: keyof T, k2: keyof T): Partial; +declare function f3(obj: T, key: keyof T | "extra"): Partial; +type MyObj = { + a: string; + b: number; + c: boolean; +}; +declare function f4(obj: MyObj, key: keyof MyObj): {}; +declare function f5(obj: T, key: keyof T): Partial; +declare function f6(obj: T, k1: keyof T, k2: keyof T, k3: keyof T): Partial; +declare function f7(obj: T, key: keyof T): { + consumed: T[keyof T]; + rest: Partial; +}; +declare function f8(obj: T, keys: Array): Partial; diff --git a/tests/baselines/reference/restSpreadingWithKeyofT.symbols b/tests/baselines/reference/restSpreadingWithKeyofT.symbols new file mode 100644 index 0000000000000..8071d79ae772a --- /dev/null +++ b/tests/baselines/reference/restSpreadingWithKeyofT.symbols @@ -0,0 +1,188 @@ +//// [tests/cases/compiler/restSpreadingWithKeyofT.ts] //// + +=== restSpreadingWithKeyofT.ts === +// Test case 1: Using keyof T directly in rest spreading +// Should result in Partial instead of Omit +function f1(obj: T, key: keyof T) { +>f1 : Symbol(f1, Decl(restSpreadingWithKeyofT.ts, 0, 0)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 2, 12)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 2, 15)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 2, 12)) +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 2, 22)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 2, 12)) + + const { [key]: removed, ...rest } = obj; +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 2, 22)) +>removed : Symbol(removed, Decl(restSpreadingWithKeyofT.ts, 3, 11)) +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 3, 27)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 2, 15)) + + return rest; +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 3, 27)) +} + +// Test case 2: Union of keyof T +// Should result in Partial since both k1 and k2 are keyof T +function f2(obj: T, k1: keyof T, k2: keyof T) { +>f2 : Symbol(f2, Decl(restSpreadingWithKeyofT.ts, 5, 1)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 9, 12)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 9, 15)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 9, 12)) +>k1 : Symbol(k1, Decl(restSpreadingWithKeyofT.ts, 9, 22)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 9, 12)) +>k2 : Symbol(k2, Decl(restSpreadingWithKeyofT.ts, 9, 35)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 9, 12)) + + const { [k1]: removed1, [k2]: removed2, ...rest } = obj; +>k1 : Symbol(k1, Decl(restSpreadingWithKeyofT.ts, 9, 22)) +>removed1 : Symbol(removed1, Decl(restSpreadingWithKeyofT.ts, 10, 11)) +>k2 : Symbol(k2, Decl(restSpreadingWithKeyofT.ts, 9, 35)) +>removed2 : Symbol(removed2, Decl(restSpreadingWithKeyofT.ts, 10, 27)) +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 10, 43)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 9, 15)) + + return rest; +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 10, 43)) +} + +// Test case 3: keyof T with additional literal +// Should still use Partial since the union contains keyof T +function f3(obj: T, key: keyof T | "extra") { +>f3 : Symbol(f3, Decl(restSpreadingWithKeyofT.ts, 12, 1)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 16, 12)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 16, 15)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 16, 12)) +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 16, 22)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 16, 12)) + + const { [key]: removed, ...rest } = obj; +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 16, 22)) +>removed : Symbol(removed, Decl(restSpreadingWithKeyofT.ts, 17, 11)) +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 17, 27)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 16, 15)) + + return rest; +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 17, 27)) +} + +// Test case 4: Specific type with keyof +type MyObj = { a: string; b: number; c: boolean; }; +>MyObj : Symbol(MyObj, Decl(restSpreadingWithKeyofT.ts, 19, 1)) +>a : Symbol(a, Decl(restSpreadingWithKeyofT.ts, 22, 14)) +>b : Symbol(b, Decl(restSpreadingWithKeyofT.ts, 22, 25)) +>c : Symbol(c, Decl(restSpreadingWithKeyofT.ts, 22, 36)) + +function f4(obj: MyObj, key: keyof MyObj) { +>f4 : Symbol(f4, Decl(restSpreadingWithKeyofT.ts, 22, 51)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 23, 12)) +>MyObj : Symbol(MyObj, Decl(restSpreadingWithKeyofT.ts, 19, 1)) +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 23, 23)) +>MyObj : Symbol(MyObj, Decl(restSpreadingWithKeyofT.ts, 19, 1)) + + const { [key]: removed, ...rest } = obj; +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 23, 23)) +>removed : Symbol(removed, Decl(restSpreadingWithKeyofT.ts, 24, 11)) +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 24, 27)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 23, 12)) + + return rest; +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 24, 27)) +} + +// Test case 5: Constraint with keyof +function f5(obj: T, key: keyof T) { +>f5 : Symbol(f5, Decl(restSpreadingWithKeyofT.ts, 26, 1)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 29, 12)) +>a : Symbol(a, Decl(restSpreadingWithKeyofT.ts, 29, 23)) +>b : Symbol(b, Decl(restSpreadingWithKeyofT.ts, 29, 34)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 29, 48)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 29, 12)) +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 29, 55)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 29, 12)) + + const { [key]: removed, ...rest } = obj; +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 29, 55)) +>removed : Symbol(removed, Decl(restSpreadingWithKeyofT.ts, 30, 11)) +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 30, 27)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 29, 48)) + + return rest; +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 30, 27)) +} + +// Test case 6: Multiple parameters with keyof in object literal +function f6(obj: T, k1: keyof T, k2: keyof T, k3: keyof T) { +>f6 : Symbol(f6, Decl(restSpreadingWithKeyofT.ts, 32, 1)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 35, 12)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 35, 15)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 35, 12)) +>k1 : Symbol(k1, Decl(restSpreadingWithKeyofT.ts, 35, 22)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 35, 12)) +>k2 : Symbol(k2, Decl(restSpreadingWithKeyofT.ts, 35, 35)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 35, 12)) +>k3 : Symbol(k3, Decl(restSpreadingWithKeyofT.ts, 35, 48)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 35, 12)) + + const { [k1]: r1, [k2]: r2, [k3]: r3, ...rest } = obj; +>k1 : Symbol(k1, Decl(restSpreadingWithKeyofT.ts, 35, 22)) +>r1 : Symbol(r1, Decl(restSpreadingWithKeyofT.ts, 36, 11)) +>k2 : Symbol(k2, Decl(restSpreadingWithKeyofT.ts, 35, 35)) +>r2 : Symbol(r2, Decl(restSpreadingWithKeyofT.ts, 36, 21)) +>k3 : Symbol(k3, Decl(restSpreadingWithKeyofT.ts, 35, 48)) +>r3 : Symbol(r3, Decl(restSpreadingWithKeyofT.ts, 36, 31)) +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 36, 41)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 35, 15)) + + return rest; +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 36, 41)) +} + +// Test case 7: Nested rest with keyof +function f7(obj: T, key: keyof T) { +>f7 : Symbol(f7, Decl(restSpreadingWithKeyofT.ts, 38, 1)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 41, 12)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 41, 15)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 41, 12)) +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 41, 22)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 41, 12)) + + const { [key]: val, ...rest } = obj; +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 41, 22)) +>val : Symbol(val, Decl(restSpreadingWithKeyofT.ts, 42, 11)) +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 42, 23)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 41, 15)) + + const consumed = val; +>consumed : Symbol(consumed, Decl(restSpreadingWithKeyofT.ts, 43, 9)) +>val : Symbol(val, Decl(restSpreadingWithKeyofT.ts, 42, 11)) + + return { consumed, rest }; +>consumed : Symbol(consumed, Decl(restSpreadingWithKeyofT.ts, 44, 12)) +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 44, 22)) +} + +// Test case 8: Array of keyof (not applicable but shows edge case) +function f8(obj: T, keys: Array) { +>f8 : Symbol(f8, Decl(restSpreadingWithKeyofT.ts, 45, 1)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 48, 12)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 48, 15)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 48, 12)) +>keys : Symbol(keys, Decl(restSpreadingWithKeyofT.ts, 48, 22)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>T : Symbol(T, Decl(restSpreadingWithKeyofT.ts, 48, 12)) + + // Can't destructure with array, but showing the type relationship + const key = keys[0]; +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 50, 9)) +>keys : Symbol(keys, Decl(restSpreadingWithKeyofT.ts, 48, 22)) + + const { [key]: removed, ...rest } = obj; +>key : Symbol(key, Decl(restSpreadingWithKeyofT.ts, 50, 9)) +>removed : Symbol(removed, Decl(restSpreadingWithKeyofT.ts, 51, 11)) +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 51, 27)) +>obj : Symbol(obj, Decl(restSpreadingWithKeyofT.ts, 48, 15)) + + return rest; +>rest : Symbol(rest, Decl(restSpreadingWithKeyofT.ts, 51, 27)) +} + diff --git a/tests/baselines/reference/restSpreadingWithKeyofT.types b/tests/baselines/reference/restSpreadingWithKeyofT.types new file mode 100644 index 0000000000000..0d3e9c4dcc491 --- /dev/null +++ b/tests/baselines/reference/restSpreadingWithKeyofT.types @@ -0,0 +1,251 @@ +//// [tests/cases/compiler/restSpreadingWithKeyofT.ts] //// + +=== restSpreadingWithKeyofT.ts === +// Test case 1: Using keyof T directly in rest spreading +// Should result in Partial instead of Omit +function f1(obj: T, key: keyof T) { +>f1 : (obj: T, key: keyof T) => Partial +> : ^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^ +>obj : T +> : ^ +>key : keyof T +> : ^^^^^^^ + + const { [key]: removed, ...rest } = obj; +>key : keyof T +> : ^^^^^^^ +>removed : T[keyof T] +> : ^^^^^^^^^^ +>rest : Partial +> : ^^^^^^^^^^ +>obj : T +> : ^ + + return rest; +>rest : Partial +> : ^^^^^^^^^^ +} + +// Test case 2: Union of keyof T +// Should result in Partial since both k1 and k2 are keyof T +function f2(obj: T, k1: keyof T, k2: keyof T) { +>f2 : (obj: T, k1: keyof T, k2: keyof T) => Partial +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^ +>obj : T +> : ^ +>k1 : keyof T +> : ^^^^^^^ +>k2 : keyof T +> : ^^^^^^^ + + const { [k1]: removed1, [k2]: removed2, ...rest } = obj; +>k1 : keyof T +> : ^^^^^^^ +>removed1 : T[keyof T] +> : ^^^^^^^^^^ +>k2 : keyof T +> : ^^^^^^^ +>removed2 : T[keyof T] +> : ^^^^^^^^^^ +>rest : Partial +> : ^^^^^^^^^^ +>obj : T +> : ^ + + return rest; +>rest : Partial +> : ^^^^^^^^^^ +} + +// Test case 3: keyof T with additional literal +// Should still use Partial since the union contains keyof T +function f3(obj: T, key: keyof T | "extra") { +>f3 : (obj: T, key: keyof T | "extra") => Partial +> : ^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^ +>obj : T +> : ^ +>key : keyof T | "extra" +> : ^^^^^^^^^^^^^^^^^ + + const { [key]: removed, ...rest } = obj; +>key : keyof T | "extra" +> : ^^^^^^^^^^^^^^^^^ +>removed : T[keyof T | "extra"] +> : ^^^^^^^^^^^^^^^^^^^^ +>rest : Partial +> : ^^^^^^^^^^ +>obj : T +> : ^ + + return rest; +>rest : Partial +> : ^^^^^^^^^^ +} + +// Test case 4: Specific type with keyof +type MyObj = { a: string; b: number; c: boolean; }; +>MyObj : MyObj +> : ^^^^^ +>a : string +> : ^^^^^^ +>b : number +> : ^^^^^^ +>c : boolean +> : ^^^^^^^ + +function f4(obj: MyObj, key: keyof MyObj) { +>f4 : (obj: MyObj, key: keyof MyObj) => {} +> : ^ ^^ ^^ ^^ ^^^^^^^ +>obj : MyObj +> : ^^^^^ +>key : keyof MyObj +> : ^^^^^^^^^^^ + + const { [key]: removed, ...rest } = obj; +>key : keyof MyObj +> : ^^^^^^^^^^^ +>removed : string | number | boolean +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>rest : {} +> : ^^ +>obj : MyObj +> : ^^^^^ + + return rest; +>rest : {} +> : ^^ +} + +// Test case 5: Constraint with keyof +function f5(obj: T, key: keyof T) { +>f5 : (obj: T, key: keyof T) => Partial +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^ +>a : string +> : ^^^^^^ +>b : number +> : ^^^^^^ +>obj : T +> : ^ +>key : keyof T +> : ^^^^^^^ + + const { [key]: removed, ...rest } = obj; +>key : keyof T +> : ^^^^^^^ +>removed : T[keyof T] +> : ^^^^^^^^^^ +>rest : Partial +> : ^^^^^^^^^^ +>obj : T +> : ^ + + return rest; +>rest : Partial +> : ^^^^^^^^^^ +} + +// Test case 6: Multiple parameters with keyof in object literal +function f6(obj: T, k1: keyof T, k2: keyof T, k3: keyof T) { +>f6 : (obj: T, k1: keyof T, k2: keyof T, k3: keyof T) => Partial +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^ +>obj : T +> : ^ +>k1 : keyof T +> : ^^^^^^^ +>k2 : keyof T +> : ^^^^^^^ +>k3 : keyof T +> : ^^^^^^^ + + const { [k1]: r1, [k2]: r2, [k3]: r3, ...rest } = obj; +>k1 : keyof T +> : ^^^^^^^ +>r1 : T[keyof T] +> : ^^^^^^^^^^ +>k2 : keyof T +> : ^^^^^^^ +>r2 : T[keyof T] +> : ^^^^^^^^^^ +>k3 : keyof T +> : ^^^^^^^ +>r3 : T[keyof T] +> : ^^^^^^^^^^ +>rest : Partial +> : ^^^^^^^^^^ +>obj : T +> : ^ + + return rest; +>rest : Partial +> : ^^^^^^^^^^ +} + +// Test case 7: Nested rest with keyof +function f7(obj: T, key: keyof T) { +>f7 : (obj: T, key: keyof T) => { consumed: T[keyof T]; rest: Partial; } +> : ^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>obj : T +> : ^ +>key : keyof T +> : ^^^^^^^ + + const { [key]: val, ...rest } = obj; +>key : keyof T +> : ^^^^^^^ +>val : T[keyof T] +> : ^^^^^^^^^^ +>rest : Partial +> : ^^^^^^^^^^ +>obj : T +> : ^ + + const consumed = val; +>consumed : T[keyof T] +> : ^^^^^^^^^^ +>val : T[keyof T] +> : ^^^^^^^^^^ + + return { consumed, rest }; +>{ consumed, rest } : { consumed: T[keyof T]; rest: Partial; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>consumed : T[keyof T] +> : ^^^^^^^^^^ +>rest : Partial +> : ^^^^^^^^^^ +} + +// Test case 8: Array of keyof (not applicable but shows edge case) +function f8(obj: T, keys: Array) { +>f8 : (obj: T, keys: Array) => Partial +> : ^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^ +>obj : T +> : ^ +>keys : (keyof T)[] +> : ^^^^^^^^^^^ + + // Can't destructure with array, but showing the type relationship + const key = keys[0]; +>key : keyof T +> : ^^^^^^^ +>keys[0] : keyof T +> : ^^^^^^^ +>keys : (keyof T)[] +> : ^^^^^^^^^^^ +>0 : 0 +> : ^ + + const { [key]: removed, ...rest } = obj; +>key : keyof T +> : ^^^^^^^ +>removed : T[keyof T] +> : ^^^^^^^^^^ +>rest : Partial +> : ^^^^^^^^^^ +>obj : T +> : ^ + + return rest; +>rest : Partial +> : ^^^^^^^^^^ +} + diff --git a/tests/cases/compiler/restSpreadingWithKeyofT.ts b/tests/cases/compiler/restSpreadingWithKeyofT.ts new file mode 100644 index 0000000000000..36c5ad6e1037a --- /dev/null +++ b/tests/cases/compiler/restSpreadingWithKeyofT.ts @@ -0,0 +1,57 @@ +// @strict: true +// @declaration: true + +// Test case 1: Using keyof T directly in rest spreading +// Should result in Partial instead of Omit +function f1(obj: T, key: keyof T) { + const { [key]: removed, ...rest } = obj; + return rest; +} + +// Test case 2: Union of keyof T +// Should result in Partial since both k1 and k2 are keyof T +function f2(obj: T, k1: keyof T, k2: keyof T) { + const { [k1]: removed1, [k2]: removed2, ...rest } = obj; + return rest; +} + +// Test case 3: keyof T with additional literal +// Should still use Partial since the union contains keyof T +function f3(obj: T, key: keyof T | "extra") { + const { [key]: removed, ...rest } = obj; + return rest; +} + +// Test case 4: Specific type with keyof +type MyObj = { a: string; b: number; c: boolean; }; +function f4(obj: MyObj, key: keyof MyObj) { + const { [key]: removed, ...rest } = obj; + return rest; +} + +// Test case 5: Constraint with keyof +function f5(obj: T, key: keyof T) { + const { [key]: removed, ...rest } = obj; + return rest; +} + +// Test case 6: Multiple parameters with keyof in object literal +function f6(obj: T, k1: keyof T, k2: keyof T, k3: keyof T) { + const { [k1]: r1, [k2]: r2, [k3]: r3, ...rest } = obj; + return rest; +} + +// Test case 7: Nested rest with keyof +function f7(obj: T, key: keyof T) { + const { [key]: val, ...rest } = obj; + const consumed = val; + return { consumed, rest }; +} + +// Test case 8: Array of keyof (not applicable but shows edge case) +function f8(obj: T, keys: Array) { + // Can't destructure with array, but showing the type relationship + const key = keys[0]; + const { [key]: removed, ...rest } = obj; + return rest; +} From 034278d7a6e9b336a872a64b77b95165f0d8f073 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Oct 2025 19:50:30 +0000 Subject: [PATCH 4/4] Add contrast test for Omit cases (not using Partial) Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- .../reference/restSpreadingWithOmit.js | 119 ++++++++++++ .../reference/restSpreadingWithOmit.symbols | 133 +++++++++++++ .../reference/restSpreadingWithOmit.types | 175 ++++++++++++++++++ tests/cases/compiler/restSpreadingWithOmit.ts | 44 +++++ 4 files changed, 471 insertions(+) create mode 100644 tests/baselines/reference/restSpreadingWithOmit.js create mode 100644 tests/baselines/reference/restSpreadingWithOmit.symbols create mode 100644 tests/baselines/reference/restSpreadingWithOmit.types create mode 100644 tests/cases/compiler/restSpreadingWithOmit.ts diff --git a/tests/baselines/reference/restSpreadingWithOmit.js b/tests/baselines/reference/restSpreadingWithOmit.js new file mode 100644 index 0000000000000..7e9e47adad6f6 --- /dev/null +++ b/tests/baselines/reference/restSpreadingWithOmit.js @@ -0,0 +1,119 @@ +//// [tests/cases/compiler/restSpreadingWithOmit.ts] //// + +//// [restSpreadingWithOmit.ts] +// This file demonstrates cases where Omit is still used (not Partial) +// These are contrast cases to restSpreadingWithKeyofT.ts + +// Test case 1: Specific literal key (not keyof T) +function f1(obj: T) { + const { a, ...rest } = obj; + return rest; // Should be Omit +} + +// Test case 2: Multiple specific literal keys +function f2(obj: T) { + const { a, b, ...rest } = obj; + return rest; // Should be Omit +} + +// Test case 3: K extends string (broader than keyof T) +function f3(obj: T, key: K) { + // This will error because K is not guaranteed to be keyof T + // but shows the type relationship +} + +// Test case 4: Mixing literal and keyof T extends +function f4(obj: T, key: K) { + const { [key]: removed, ...rest } = obj; + return rest; // Should be Omit, not Partial because K is a specific subset of keyof T +} + +// Test case 5: Using specific keys from constrained type +type Props = { x: number; y: number; z: number }; +function f5(obj: T) { + const { x, y, ...rest } = obj; + return rest; // Should be Omit +} + +// Test case 6: Symbol keys +const sym1 = Symbol(); +const sym2 = Symbol(); +function f6(obj: T) { + const { [sym1]: s1, ...rest } = obj; + return rest; // Should be Omit +} + + +//// [restSpreadingWithOmit.js] +"use strict"; +// This file demonstrates cases where Omit is still used (not Partial) +// These are contrast cases to restSpreadingWithKeyofT.ts +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +// Test case 1: Specific literal key (not keyof T) +function f1(obj) { + var a = obj.a, rest = __rest(obj, ["a"]); + return rest; // Should be Omit +} +// Test case 2: Multiple specific literal keys +function f2(obj) { + var a = obj.a, b = obj.b, rest = __rest(obj, ["a", "b"]); + return rest; // Should be Omit +} +// Test case 3: K extends string (broader than keyof T) +function f3(obj, key) { + // This will error because K is not guaranteed to be keyof T + // but shows the type relationship +} +// Test case 4: Mixing literal and keyof T extends +function f4(obj, key) { + var _a = obj, _b = key, removed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); + return rest; // Should be Omit, not Partial because K is a specific subset of keyof T +} +function f5(obj) { + var x = obj.x, y = obj.y, rest = __rest(obj, ["x", "y"]); + return rest; // Should be Omit +} +// Test case 6: Symbol keys +var sym1 = Symbol(); +var sym2 = Symbol(); +function f6(obj) { + var _a = obj, _b = sym1, s1 = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]); + return rest; // Should be Omit +} + + +//// [restSpreadingWithOmit.d.ts] +declare function f1(obj: T): Omit; +declare function f2(obj: T): Omit; +declare function f3(obj: T, key: K): void; +declare function f4(obj: T, key: K): Omit; +type Props = { + x: number; + y: number; + z: number; +}; +declare function f5(obj: T): Omit; +declare const sym1: unique symbol; +declare const sym2: unique symbol; +declare function f6(obj: T): Omit; diff --git a/tests/baselines/reference/restSpreadingWithOmit.symbols b/tests/baselines/reference/restSpreadingWithOmit.symbols new file mode 100644 index 0000000000000..021621a390160 --- /dev/null +++ b/tests/baselines/reference/restSpreadingWithOmit.symbols @@ -0,0 +1,133 @@ +//// [tests/cases/compiler/restSpreadingWithOmit.ts] //// + +=== restSpreadingWithOmit.ts === +// This file demonstrates cases where Omit is still used (not Partial) +// These are contrast cases to restSpreadingWithKeyofT.ts + +// Test case 1: Specific literal key (not keyof T) +function f1(obj: T) { +>f1 : Symbol(f1, Decl(restSpreadingWithOmit.ts, 0, 0)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 4, 12)) +>a : Symbol(a, Decl(restSpreadingWithOmit.ts, 4, 23)) +>b : Symbol(b, Decl(restSpreadingWithOmit.ts, 4, 34)) +>c : Symbol(c, Decl(restSpreadingWithOmit.ts, 4, 45)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 4, 60)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 4, 12)) + + const { a, ...rest } = obj; +>a : Symbol(a, Decl(restSpreadingWithOmit.ts, 5, 11)) +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 5, 14)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 4, 60)) + + return rest; // Should be Omit +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 5, 14)) +} + +// Test case 2: Multiple specific literal keys +function f2(obj: T) { +>f2 : Symbol(f2, Decl(restSpreadingWithOmit.ts, 7, 1)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 10, 12)) +>a : Symbol(a, Decl(restSpreadingWithOmit.ts, 10, 23)) +>b : Symbol(b, Decl(restSpreadingWithOmit.ts, 10, 34)) +>c : Symbol(c, Decl(restSpreadingWithOmit.ts, 10, 45)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 10, 60)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 10, 12)) + + const { a, b, ...rest } = obj; +>a : Symbol(a, Decl(restSpreadingWithOmit.ts, 11, 11)) +>b : Symbol(b, Decl(restSpreadingWithOmit.ts, 11, 14)) +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 11, 17)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 10, 60)) + + return rest; // Should be Omit +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 11, 17)) +} + +// Test case 3: K extends string (broader than keyof T) +function f3(obj: T, key: K) { +>f3 : Symbol(f3, Decl(restSpreadingWithOmit.ts, 13, 1)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 16, 12)) +>K : Symbol(K, Decl(restSpreadingWithOmit.ts, 16, 14)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 16, 33)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 16, 12)) +>key : Symbol(key, Decl(restSpreadingWithOmit.ts, 16, 40)) +>K : Symbol(K, Decl(restSpreadingWithOmit.ts, 16, 14)) + + // This will error because K is not guaranteed to be keyof T + // but shows the type relationship +} + +// Test case 4: Mixing literal and keyof T extends +function f4(obj: T, key: K) { +>f4 : Symbol(f4, Decl(restSpreadingWithOmit.ts, 19, 1)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 22, 12)) +>K : Symbol(K, Decl(restSpreadingWithOmit.ts, 22, 14)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 22, 12)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 22, 34)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 22, 12)) +>key : Symbol(key, Decl(restSpreadingWithOmit.ts, 22, 41)) +>K : Symbol(K, Decl(restSpreadingWithOmit.ts, 22, 14)) + + const { [key]: removed, ...rest } = obj; +>key : Symbol(key, Decl(restSpreadingWithOmit.ts, 22, 41)) +>removed : Symbol(removed, Decl(restSpreadingWithOmit.ts, 23, 11)) +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 23, 27)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 22, 34)) + + return rest; // Should be Omit, not Partial because K is a specific subset of keyof T +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 23, 27)) +} + +// Test case 5: Using specific keys from constrained type +type Props = { x: number; y: number; z: number }; +>Props : Symbol(Props, Decl(restSpreadingWithOmit.ts, 25, 1)) +>x : Symbol(x, Decl(restSpreadingWithOmit.ts, 28, 14)) +>y : Symbol(y, Decl(restSpreadingWithOmit.ts, 28, 25)) +>z : Symbol(z, Decl(restSpreadingWithOmit.ts, 28, 36)) + +function f5(obj: T) { +>f5 : Symbol(f5, Decl(restSpreadingWithOmit.ts, 28, 49)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 29, 12)) +>Props : Symbol(Props, Decl(restSpreadingWithOmit.ts, 25, 1)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 29, 29)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 29, 12)) + + const { x, y, ...rest } = obj; +>x : Symbol(x, Decl(restSpreadingWithOmit.ts, 30, 11)) +>y : Symbol(y, Decl(restSpreadingWithOmit.ts, 30, 14)) +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 30, 17)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 29, 29)) + + return rest; // Should be Omit +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 30, 17)) +} + +// Test case 6: Symbol keys +const sym1 = Symbol(); +>sym1 : Symbol(sym1, Decl(restSpreadingWithOmit.ts, 35, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +const sym2 = Symbol(); +>sym2 : Symbol(sym2, Decl(restSpreadingWithOmit.ts, 36, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +function f6(obj: T) { +>f6 : Symbol(f6, Decl(restSpreadingWithOmit.ts, 36, 22)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 37, 12)) +>[sym1] : Symbol([sym1], Decl(restSpreadingWithOmit.ts, 37, 23)) +>sym1 : Symbol(sym1, Decl(restSpreadingWithOmit.ts, 35, 5)) +>[sym2] : Symbol([sym2], Decl(restSpreadingWithOmit.ts, 37, 39)) +>sym2 : Symbol(sym2, Decl(restSpreadingWithOmit.ts, 36, 5)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 37, 58)) +>T : Symbol(T, Decl(restSpreadingWithOmit.ts, 37, 12)) + + const { [sym1]: s1, ...rest } = obj; +>sym1 : Symbol(sym1, Decl(restSpreadingWithOmit.ts, 35, 5)) +>s1 : Symbol(s1, Decl(restSpreadingWithOmit.ts, 38, 11)) +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 38, 23)) +>obj : Symbol(obj, Decl(restSpreadingWithOmit.ts, 37, 58)) + + return rest; // Should be Omit +>rest : Symbol(rest, Decl(restSpreadingWithOmit.ts, 38, 23)) +} + diff --git a/tests/baselines/reference/restSpreadingWithOmit.types b/tests/baselines/reference/restSpreadingWithOmit.types new file mode 100644 index 0000000000000..28d3f310a4676 --- /dev/null +++ b/tests/baselines/reference/restSpreadingWithOmit.types @@ -0,0 +1,175 @@ +//// [tests/cases/compiler/restSpreadingWithOmit.ts] //// + +=== restSpreadingWithOmit.ts === +// This file demonstrates cases where Omit is still used (not Partial) +// These are contrast cases to restSpreadingWithKeyofT.ts + +// Test case 1: Specific literal key (not keyof T) +function f1(obj: T) { +>f1 : (obj: T) => Omit +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^ +>a : string +> : ^^^^^^ +>b : number +> : ^^^^^^ +>c : boolean +> : ^^^^^^^ +>obj : T +> : ^ + + const { a, ...rest } = obj; +>a : string +> : ^^^^^^ +>rest : Omit +> : ^^^^^^^^^^^^ +>obj : T +> : ^ + + return rest; // Should be Omit +>rest : Omit +> : ^^^^^^^^^^^^ +} + +// Test case 2: Multiple specific literal keys +function f2(obj: T) { +>f2 : (obj: T) => Omit +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ +>a : string +> : ^^^^^^ +>b : number +> : ^^^^^^ +>c : boolean +> : ^^^^^^^ +>obj : T +> : ^ + + const { a, b, ...rest } = obj; +>a : string +> : ^^^^^^ +>b : number +> : ^^^^^^ +>rest : Omit +> : ^^^^^^^^^^^^^^^^^^ +>obj : T +> : ^ + + return rest; // Should be Omit +>rest : Omit +> : ^^^^^^^^^^^^^^^^^^ +} + +// Test case 3: K extends string (broader than keyof T) +function f3(obj: T, key: K) { +>f3 : (obj: T, key: K) => void +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^^^^^ +>obj : T +> : ^ +>key : K +> : ^ + + // This will error because K is not guaranteed to be keyof T + // but shows the type relationship +} + +// Test case 4: Mixing literal and keyof T extends +function f4(obj: T, key: K) { +>f4 : (obj: T, key: K) => Omit +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^ +>obj : T +> : ^ +>key : K +> : ^ + + const { [key]: removed, ...rest } = obj; +>key : K +> : ^ +>removed : T[K] +> : ^^^^ +>rest : Omit +> : ^^^^^^^^^^ +>obj : T +> : ^ + + return rest; // Should be Omit, not Partial because K is a specific subset of keyof T +>rest : Omit +> : ^^^^^^^^^^ +} + +// Test case 5: Using specific keys from constrained type +type Props = { x: number; y: number; z: number }; +>Props : Props +> : ^^^^^ +>x : number +> : ^^^^^^ +>y : number +> : ^^^^^^ +>z : number +> : ^^^^^^ + +function f5(obj: T) { +>f5 : (obj: T) => Omit +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ +>obj : T +> : ^ + + const { x, y, ...rest } = obj; +>x : number +> : ^^^^^^ +>y : number +> : ^^^^^^ +>rest : Omit +> : ^^^^^^^^^^^^^^^^^^ +>obj : T +> : ^ + + return rest; // Should be Omit +>rest : Omit +> : ^^^^^^^^^^^^^^^^^^ +} + +// Test case 6: Symbol keys +const sym1 = Symbol(); +>sym1 : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol() : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol : SymbolConstructor +> : ^^^^^^^^^^^^^^^^^ + +const sym2 = Symbol(); +>sym2 : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol() : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol : SymbolConstructor +> : ^^^^^^^^^^^^^^^^^ + +function f6(obj: T) { +>f6 : (obj: T) => Omit +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>[sym1] : string +> : ^^^^^^ +>sym1 : unique symbol +> : ^^^^^^^^^^^^^ +>[sym2] : number +> : ^^^^^^ +>sym2 : unique symbol +> : ^^^^^^^^^^^^^ +>obj : T +> : ^ + + const { [sym1]: s1, ...rest } = obj; +>sym1 : unique symbol +> : ^^^^^^^^^^^^^ +>s1 : string +> : ^^^^^^ +>rest : Omit +> : ^^^^^^^^^^^^^^^^^^^^^^ +>obj : T +> : ^ + + return rest; // Should be Omit +>rest : Omit +> : ^^^^^^^^^^^^^^^^^^^^^^ +} + diff --git a/tests/cases/compiler/restSpreadingWithOmit.ts b/tests/cases/compiler/restSpreadingWithOmit.ts new file mode 100644 index 0000000000000..4af5770c18154 --- /dev/null +++ b/tests/cases/compiler/restSpreadingWithOmit.ts @@ -0,0 +1,44 @@ +// @strict: true +// @declaration: true + +// This file demonstrates cases where Omit is still used (not Partial) +// These are contrast cases to restSpreadingWithKeyofT.ts + +// Test case 1: Specific literal key (not keyof T) +function f1(obj: T) { + const { a, ...rest } = obj; + return rest; // Should be Omit +} + +// Test case 2: Multiple specific literal keys +function f2(obj: T) { + const { a, b, ...rest } = obj; + return rest; // Should be Omit +} + +// Test case 3: K extends string (broader than keyof T) +function f3(obj: T, key: K) { + // This will error because K is not guaranteed to be keyof T + // but shows the type relationship +} + +// Test case 4: Mixing literal and keyof T extends +function f4(obj: T, key: K) { + const { [key]: removed, ...rest } = obj; + return rest; // Should be Omit, not Partial because K is a specific subset of keyof T +} + +// Test case 5: Using specific keys from constrained type +type Props = { x: number; y: number; z: number }; +function f5(obj: T) { + const { x, y, ...rest } = obj; + return rest; // Should be Omit +} + +// Test case 6: Symbol keys +const sym1 = Symbol(); +const sym2 = Symbol(); +function f6(obj: T) { + const { [sym1]: s1, ...rest } = obj; + return rest; // Should be Omit +}