From 1d71bc3054b55c7b9555c6b035b4078b5e3f8ae1 Mon Sep 17 00:00:00 2001 From: timurzvural Date: Thu, 29 Oct 2026 19:35:44 +0000 Subject: [PATCH] fix(65011): preserve contextual types for destructured parameter defaults When a parameter uses a binding pattern with `= {}` / `= []`, do not pad the initializer's object/tuple type if the parameter already has a contextual type. Padding invented any-typed properties and caused TS7031 on binding elements (7.1 regression vs 7.0). Also covers the related case from #64974. Adds compiler tests for the JSDoc/@returns and annotated-function shapes from #65011. AI assistance disclosure: authored with Cursor Agent. --- tsc/internal/checker/checker.go | 12 +- ...extualDestructuredParameterDefault.symbols | 55 ++++ ...ntextualDestructuredParameterDefault.types | 65 +++++ ...ngPatternDefaultMissingElements.errors.txt | 100 +++++++- ...ndingPatternDefaultMissingElements.symbols | 181 ++++++++++++++ ...BindingPatternDefaultMissingElements.types | 235 ++++++++++++++++++ .../contextualDestructuredParameterDefault.ts | 31 +++ ...ectBindingPatternDefaultMissingElements.ts | 52 ++++ 8 files changed, 727 insertions(+), 4 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/contextualDestructuredParameterDefault.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/contextualDestructuredParameterDefault.types create mode 100644 tsc/testdata/tests/cases/compiler/contextualDestructuredParameterDefault.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 086779ceb115a..798902e01ceef 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -17233,15 +17233,21 @@ func (c *Checker) checkDeclarationInitializer(declaration *ast.Node, checkMode C t = c.checkExpressionCachedEx(initializer, checkMode) } } - if ast.IsParameterDeclaration(ast.GetRootDeclaration(declaration)) { + // When a destructured parameter has a default like `= {}`, padding the + // initializer's object/tuple type with missing binding elements would + // invent `any`-typed properties and erase a contextual parameter type + // (TS7031). Only pad when there is no contextual type for the parameter. + // See https://github.com/microsoft/TypeScript/issues/65011 and #64974. + root := ast.GetRootDeclaration(declaration) + if ast.IsParameterDeclaration(root) { name := declaration.Name() switch name.Kind { case ast.KindObjectBindingPattern: - if isObjectLiteralType(t) { + if isObjectLiteralType(t) && c.getContextualTypeForVariableLikeDeclaration(root, ContextFlagsNone) == nil { return c.padObjectLiteralType(t, name) } case ast.KindArrayBindingPattern: - if isTupleType(t) { + if isTupleType(t) && c.getContextualTypeForVariableLikeDeclaration(root, ContextFlagsNone) == nil { return c.padTupleType(t, name) } } diff --git a/tsc/testdata/baselines/reference/compiler/contextualDestructuredParameterDefault.symbols b/tsc/testdata/baselines/reference/compiler/contextualDestructuredParameterDefault.symbols new file mode 100644 index 0000000000000..56f417cbd894b --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contextualDestructuredParameterDefault.symbols @@ -0,0 +1,55 @@ +//// [tests/cases/compiler/contextualDestructuredParameterDefault.ts] //// + +=== /a.js === +/** @typedef {{ signal?: AbortSignal }} Opts */ +/** @typedef {{ structure: (c: string, o?: Opts) => Promise }} Adapter */ +/** @returns {Adapter} */ +export function make() { +>make : Symbol(make, Decl(a.js, 0, 0)) + + return { + async structure(c, { signal } = {}) { +>structure : Symbol(structure, Decl(a.js, 4, 10)) +>c : Symbol(c, Decl(a.js, 5, 20)) +>signal : Symbol(signal, Decl(a.js, 5, 24)) + + signal?.throwIfAborted() +>signal?.throwIfAborted : Symbol(AbortSignal.throwIfAborted, Decl(lib.dom.d.ts, --, --)) +>signal : Symbol(signal, Decl(a.js, 5, 24)) +>throwIfAborted : Symbol(AbortSignal.throwIfAborted, Decl(lib.dom.d.ts, --, --)) + + return [c] +>c : Symbol(c, Decl(a.js, 5, 20)) + + }, + } +} + +/** @typedef {(n: number, o?: Opts) => Promise} Fn */ +export const p = /** @type {Fn} */ (async (n, { signal } = {}) => { +>p : Symbol(p, Decl(a.js, 13, 12)) +>n : Symbol(n, Decl(a.js, 13, 43)) +>signal : Symbol(signal, Decl(a.js, 13, 47)) + + signal?.throwIfAborted() +>signal?.throwIfAborted : Symbol(AbortSignal.throwIfAborted, Decl(lib.dom.d.ts, --, --)) +>signal : Symbol(signal, Decl(a.js, 13, 47)) +>throwIfAborted : Symbol(AbortSignal.throwIfAborted, Decl(lib.dom.d.ts, --, --)) + +}) + +=== /b.ts === +type O = { signal?: AbortSignal } +>O : Symbol(O, Decl(b.ts, 0, 0)) +>signal : Symbol(signal, Decl(b.ts, 0, 10)) +>AbortSignal : Symbol(AbortSignal, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --)) + +const f: (o?: O) => void = ({ signal } = {}) => { signal?.throwIfAborted() } +>f : Symbol(f, Decl(b.ts, 1, 5)) +>o : Symbol(o, Decl(b.ts, 1, 10)) +>O : Symbol(O, Decl(b.ts, 0, 0)) +>signal : Symbol(signal, Decl(b.ts, 1, 29)) +>signal?.throwIfAborted : Symbol(AbortSignal.throwIfAborted, Decl(lib.dom.d.ts, --, --)) +>signal : Symbol(signal, Decl(b.ts, 1, 29)) +>throwIfAborted : Symbol(AbortSignal.throwIfAborted, Decl(lib.dom.d.ts, --, --)) + diff --git a/tsc/testdata/baselines/reference/compiler/contextualDestructuredParameterDefault.types b/tsc/testdata/baselines/reference/compiler/contextualDestructuredParameterDefault.types new file mode 100644 index 0000000000000..4efa7f9caf3e8 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/contextualDestructuredParameterDefault.types @@ -0,0 +1,65 @@ +//// [tests/cases/compiler/contextualDestructuredParameterDefault.ts] //// + +=== /a.js === +/** @typedef {{ signal?: AbortSignal }} Opts */ +/** @typedef {{ structure: (c: string, o?: Opts) => Promise }} Adapter */ +/** @returns {Adapter} */ +export function make() { +>make : () => Adapter + + return { +>{ async structure(c, { signal } = {}) { signal?.throwIfAborted() return [c] }, } : { structure(c: string, { signal }?: Opts | undefined): Promise; } + + async structure(c, { signal } = {}) { +>structure : (c: string, { signal }?: Opts | undefined) => Promise +>c : string +>signal : AbortSignal | undefined +>{} : {} + + signal?.throwIfAborted() +>signal?.throwIfAborted() : void | undefined +>signal?.throwIfAborted : (() => void) | undefined +>signal : AbortSignal | undefined +>throwIfAborted : (() => void) | undefined + + return [c] +>[c] : string[] +>c : string + + }, + } +} + +/** @typedef {(n: number, o?: Opts) => Promise} Fn */ +export const p = /** @type {Fn} */ (async (n, { signal } = {}) => { +>p : Fn +>(async (n, { signal } = {}) => { signal?.throwIfAborted()}) : Fn +>async (n, { signal } = {}) => { signal?.throwIfAborted()} : (n: number, { signal }?: Opts | undefined) => Promise +>n : number +>signal : AbortSignal | undefined +>{} : {} + + signal?.throwIfAborted() +>signal?.throwIfAborted() : void | undefined +>signal?.throwIfAborted : (() => void) | undefined +>signal : AbortSignal | undefined +>throwIfAborted : (() => void) | undefined + +}) + +=== /b.ts === +type O = { signal?: AbortSignal } +>O : O +>signal : AbortSignal | undefined + +const f: (o?: O) => void = ({ signal } = {}) => { signal?.throwIfAborted() } +>f : (o?: O) => void +>o : O | undefined +>({ signal } = {}) => { signal?.throwIfAborted() } : ({ signal }?: O | undefined) => void +>signal : AbortSignal | undefined +>{} : {} +>signal?.throwIfAborted() : void | undefined +>signal?.throwIfAborted : (() => void) | undefined +>signal : AbortSignal | undefined +>throwIfAborted : (() => void) | undefined + diff --git a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.errors.txt b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.errors.txt index 823bd410ef0cb..03e40d917cf07 100644 --- a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.errors.txt @@ -3,6 +3,24 @@ /main.ts(5,5): error TS7031: Binding element 'required' implicitly has an 'any' type. /main.ts(10,5): error TS7031: Binding element 'required' implicitly has an 'any' type. /main.ts(15,5): error TS7031: Binding element 'required' implicitly has an 'any' type. +/main.ts(36,11): error TS2322: Type 'string | undefined' is not assignable to type 'number'. + Type 'undefined' is not assignable to type 'number'. +/main.ts(37,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +/main.ts(42,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +/main.ts(47,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +/main.ts(51,59): error TS2339: Property 'b' does not exist on type '{ a?: number | undefined; }'. +/main.ts(52,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +/main.ts(56,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +/main.ts(60,11): error TS2322: Type 'number' is not assignable to type 'string'. +/main.ts(69,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +/main.ts(73,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. ==== /unchecked.js (0 errors) ==== @@ -32,7 +50,7 @@ optional = false, ] = []) => {}; -==== /main.ts (3 errors) ==== +==== /main.ts (13 errors) ==== import { checkedArray, checkedObject } from "./checked"; import { uncheckedArray, uncheckedObject } from "./unchecked"; @@ -65,4 +83,84 @@ checkedArray(["value"]); typedObject({ required: "value" }); typedArray(["value"]); + + // Contextual typing must win over padding for `= {}` defaults (#64974 / #65011). + type F = (input?: { a?: string; b?: number }) => number; + export const f1: F = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); + + export const f2: (input: { a?: string; b?: number }) => number = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); + + declare function f3(callback: F): void; + f3(({ a, b } = {}) => { + const c: number = a; + ~ +!!! error TS2322: Type 'string | undefined' is not assignable to type 'number'. +!!! error TS2322: Type 'undefined' is not assignable to type 'number'. + const d: string = b; + ~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + return 0; + }); + + export const f4: (input?: { a?: { b?: number } }) => number = ({ a: { b } = {} } = {}) => { + const c: string = b; + ~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + return b ?? 0; + }; + + export function f5({ a: { b } = {} }: { a?: { b?: number } }) { + const c: string = b; + ~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + return b; + } + + export const f6: (input?: { a?: number }) => void = ({ a, b } = {}) => { + ~ +!!! error TS2339: Property 'b' does not exist on type '{ a?: number | undefined; }'. + const c: string = a; + ~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + }; + + export const f7: (input?: [{ a?: number }?]) => void = ([{ a } = {}] = []) => { + const b: string = a; + ~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + }; + + export const f8: (input?: { [key: string]: number }) => void = ({ a } = {}) => { + const b: string = a; + ~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + }; + + export const f9: (input?: any) => void = ({ a: { b } = {} } = {}) => { + b; + }; + + declare function f10(value: T, callback: (input?: Partial) => void): void; + f10({ a: 1 }, ({ a } = {}) => { + const b: string = a; + ~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + }); + + export const f11: (input?: { a?: number }[]) => void = ([...[{ a } = {}]] = []) => { + const b: string = a; + ~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. + }; + + export const f12: (input: { a: boolean; b?: string }) => string = ({ a, b } = { a: false }) => `${a}${b}`; + + export const f13 = (input: { a?: number; b?: string }[]) => input.map(({ a, b } = {}) => `${a}${b}`); \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.symbols b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.symbols index 5542ea8d095bf..69d9ea2264613 100644 --- a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.symbols +++ b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.symbols @@ -114,3 +114,184 @@ typedObject({ required: "value" }); typedArray(["value"]); >typedArray : Symbol(typedArray, Decl(main.ts, 8, 12)) +// Contextual typing must win over padding for `= {}` defaults (#64974 / #65011). +type F = (input?: { a?: string; b?: number }) => number; +>F : Symbol(F, Decl(main.ts, 25, 22)) +>input : Symbol(input, Decl(main.ts, 28, 10)) +>a : Symbol(a, Decl(main.ts, 28, 19)) +>b : Symbol(b, Decl(main.ts, 28, 31)) + +export const f1: F = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); +>f1 : Symbol(f1, Decl(main.ts, 29, 12)) +>F : Symbol(F, Decl(main.ts, 25, 22)) +>a : Symbol(a, Decl(main.ts, 29, 23)) +>b : Symbol(b, Decl(main.ts, 29, 26)) +>a : Symbol(a, Decl(main.ts, 29, 23)) +>b : Symbol(b, Decl(main.ts, 29, 26)) + +export const f2: (input: { a?: string; b?: number }) => number = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); +>f2 : Symbol(f2, Decl(main.ts, 31, 12)) +>input : Symbol(input, Decl(main.ts, 31, 18)) +>a : Symbol(a, Decl(main.ts, 31, 26)) +>b : Symbol(b, Decl(main.ts, 31, 38)) +>a : Symbol(a, Decl(main.ts, 31, 67)) +>b : Symbol(b, Decl(main.ts, 31, 70)) +>a : Symbol(a, Decl(main.ts, 31, 67)) +>b : Symbol(b, Decl(main.ts, 31, 70)) + +declare function f3(callback: F): void; +>f3 : Symbol(f3, Decl(main.ts, 31, 107)) +>callback : Symbol(callback, Decl(main.ts, 33, 20)) +>F : Symbol(F, Decl(main.ts, 25, 22)) + +f3(({ a, b } = {}) => { +>f3 : Symbol(f3, Decl(main.ts, 31, 107)) +>a : Symbol(a, Decl(main.ts, 34, 5)) +>b : Symbol(b, Decl(main.ts, 34, 8)) + + const c: number = a; +>c : Symbol(c, Decl(main.ts, 35, 9)) +>a : Symbol(a, Decl(main.ts, 34, 5)) + + const d: string = b; +>d : Symbol(d, Decl(main.ts, 36, 9)) +>b : Symbol(b, Decl(main.ts, 34, 8)) + + return 0; +}); + +export const f4: (input?: { a?: { b?: number } }) => number = ({ a: { b } = {} } = {}) => { +>f4 : Symbol(f4, Decl(main.ts, 40, 12)) +>input : Symbol(input, Decl(main.ts, 40, 18)) +>a : Symbol(a, Decl(main.ts, 40, 27)) +>b : Symbol(b, Decl(main.ts, 40, 33)) +>b : Symbol(b, Decl(main.ts, 40, 69)) + + const c: string = b; +>c : Symbol(c, Decl(main.ts, 41, 9)) +>b : Symbol(b, Decl(main.ts, 40, 69)) + + return b ?? 0; +>b : Symbol(b, Decl(main.ts, 40, 69)) + +}; + +export function f5({ a: { b } = {} }: { a?: { b?: number } }) { +>f5 : Symbol(f5, Decl(main.ts, 43, 2)) +>a : Symbol(a, Decl(main.ts, 45, 39)) +>b : Symbol(b, Decl(main.ts, 45, 25)) +>a : Symbol(a, Decl(main.ts, 45, 39)) +>b : Symbol(b, Decl(main.ts, 45, 45)) + + const c: string = b; +>c : Symbol(c, Decl(main.ts, 46, 9)) +>b : Symbol(b, Decl(main.ts, 45, 25)) + + return b; +>b : Symbol(b, Decl(main.ts, 45, 25)) +} + +export const f6: (input?: { a?: number }) => void = ({ a, b } = {}) => { +>f6 : Symbol(f6, Decl(main.ts, 50, 12)) +>input : Symbol(input, Decl(main.ts, 50, 18)) +>a : Symbol(a, Decl(main.ts, 50, 27)) +>a : Symbol(a, Decl(main.ts, 50, 54)) +>b : Symbol(b, Decl(main.ts, 50, 57)) + + const c: string = a; +>c : Symbol(c, Decl(main.ts, 51, 9)) +>a : Symbol(a, Decl(main.ts, 50, 54)) + +}; + +export const f7: (input?: [{ a?: number }?]) => void = ([{ a } = {}] = []) => { +>f7 : Symbol(f7, Decl(main.ts, 54, 12)) +>input : Symbol(input, Decl(main.ts, 54, 18)) +>a : Symbol(a, Decl(main.ts, 54, 28)) +>a : Symbol(a, Decl(main.ts, 54, 58)) + + const b: string = a; +>b : Symbol(b, Decl(main.ts, 55, 9)) +>a : Symbol(a, Decl(main.ts, 54, 58)) + +}; + +export const f8: (input?: { [key: string]: number }) => void = ({ a } = {}) => { +>f8 : Symbol(f8, Decl(main.ts, 58, 12)) +>input : Symbol(input, Decl(main.ts, 58, 18)) +>key : Symbol(key, Decl(main.ts, 58, 29)) +>a : Symbol(a, Decl(main.ts, 58, 65)) + + const b: string = a; +>b : Symbol(b, Decl(main.ts, 59, 9)) +>a : Symbol(a, Decl(main.ts, 58, 65)) + +}; + +export const f9: (input?: any) => void = ({ a: { b } = {} } = {}) => { +>f9 : Symbol(f9, Decl(main.ts, 62, 12)) +>input : Symbol(input, Decl(main.ts, 62, 18)) +>b : Symbol(b, Decl(main.ts, 62, 48)) + + b; +>b : Symbol(b, Decl(main.ts, 62, 48)) + +}; + +declare function f10(value: T, callback: (input?: Partial) => void): void; +>f10 : Symbol(f10, Decl(main.ts, 64, 2)) +>T : Symbol(T, Decl(main.ts, 66, 21)) +>value : Symbol(value, Decl(main.ts, 66, 24)) +>T : Symbol(T, Decl(main.ts, 66, 21)) +>callback : Symbol(callback, Decl(main.ts, 66, 33)) +>input : Symbol(input, Decl(main.ts, 66, 45)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 66, 21)) + +f10({ a: 1 }, ({ a } = {}) => { +>f10 : Symbol(f10, Decl(main.ts, 64, 2)) +>a : Symbol(a, Decl(main.ts, 67, 5)) +>a : Symbol(a, Decl(main.ts, 67, 16)) + + const b: string = a; +>b : Symbol(b, Decl(main.ts, 68, 9)) +>a : Symbol(a, Decl(main.ts, 67, 16)) + +}); + +export const f11: (input?: { a?: number }[]) => void = ([...[{ a } = {}]] = []) => { +>f11 : Symbol(f11, Decl(main.ts, 71, 12)) +>input : Symbol(input, Decl(main.ts, 71, 19)) +>a : Symbol(a, Decl(main.ts, 71, 28)) +>a : Symbol(a, Decl(main.ts, 71, 62)) + + const b: string = a; +>b : Symbol(b, Decl(main.ts, 72, 9)) +>a : Symbol(a, Decl(main.ts, 71, 62)) + +}; + +export const f12: (input: { a: boolean; b?: string }) => string = ({ a, b } = { a: false }) => `${a}${b}`; +>f12 : Symbol(f12, Decl(main.ts, 75, 12)) +>input : Symbol(input, Decl(main.ts, 75, 19)) +>a : Symbol(a, Decl(main.ts, 75, 27)) +>b : Symbol(b, Decl(main.ts, 75, 39)) +>a : Symbol(a, Decl(main.ts, 75, 68)) +>b : Symbol(b, Decl(main.ts, 75, 71)) +>a : Symbol(a, Decl(main.ts, 75, 79)) +>a : Symbol(a, Decl(main.ts, 75, 68)) +>b : Symbol(b, Decl(main.ts, 75, 71)) + +export const f13 = (input: { a?: number; b?: string }[]) => input.map(({ a, b } = {}) => `${a}${b}`); +>f13 : Symbol(f13, Decl(main.ts, 77, 12)) +>input : Symbol(input, Decl(main.ts, 77, 20)) +>a : Symbol(a, Decl(main.ts, 77, 28)) +>b : Symbol(b, Decl(main.ts, 77, 40)) +>input.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>input : Symbol(input, Decl(main.ts, 77, 20)) +>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(main.ts, 77, 72)) +>b : Symbol(b, Decl(main.ts, 77, 75)) +>a : Symbol(a, Decl(main.ts, 77, 72)) +>b : Symbol(b, Decl(main.ts, 77, 75)) + diff --git a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.types b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.types index 1791cbfaebf14..4d6dd1222f034 100644 --- a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.types +++ b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.types @@ -153,3 +153,238 @@ typedArray(["value"]); >["value"] : [string] >"value" : "value" +// Contextual typing must win over padding for `= {}` defaults (#64974 / #65011). +type F = (input?: { a?: string; b?: number }) => number; +>F : F +>input : { a?: string; b?: number; } | undefined +>a : string | undefined +>b : number | undefined + +export const f1: F = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); +>f1 : F +>({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0) : ({ a, b }?: { a?: string; b?: number; } | undefined) => number +>a : string | undefined +>b : number | undefined +>{} : {} +>(a ? 1 : 0) + (b ?? 0) : number +>(a ? 1 : 0) : 0 | 1 +>a ? 1 : 0 : 0 | 1 +>a : string | undefined +>1 : 1 +>0 : 0 +>(b ?? 0) : number +>b ?? 0 : number +>b : number | undefined +>0 : 0 + +export const f2: (input: { a?: string; b?: number }) => number = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); +>f2 : (input: { a?: string; b?: number; }) => number +>input : { a?: string; b?: number; } +>a : string | undefined +>b : number | undefined +>({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0) : ({ a, b }?: { a?: string; b?: number; }) => number +>a : string | undefined +>b : number | undefined +>{} : {} +>(a ? 1 : 0) + (b ?? 0) : number +>(a ? 1 : 0) : 0 | 1 +>a ? 1 : 0 : 0 | 1 +>a : string | undefined +>1 : 1 +>0 : 0 +>(b ?? 0) : number +>b ?? 0 : number +>b : number | undefined +>0 : 0 + +declare function f3(callback: F): void; +>f3 : (callback: F) => void +>callback : F + +f3(({ a, b } = {}) => { +>f3(({ a, b } = {}) => { const c: number = a; const d: string = b; return 0;}) : void +>f3 : (callback: F) => void +>({ a, b } = {}) => { const c: number = a; const d: string = b; return 0;} : ({ a, b }?: { a?: string; b?: number; } | undefined) => number +>a : string | undefined +>b : number | undefined +>{} : {} + + const c: number = a; +>c : number +>a : string | undefined + + const d: string = b; +>d : string +>b : number | undefined + + return 0; +>0 : 0 + +}); + +export const f4: (input?: { a?: { b?: number } }) => number = ({ a: { b } = {} } = {}) => { +>f4 : (input?: { a?: { b?: number; }; }) => number +>input : { a?: { b?: number; }; } | undefined +>a : { b?: number; } | undefined +>b : number | undefined +>({ a: { b } = {} } = {}) => { const c: string = b; return b ?? 0;} : ({ a: { b } }?: { a?: { b?: number; }; } | undefined) => number +>a : any +>b : number | undefined +>{} : {} +>{} : {} + + const c: string = b; +>c : string +>b : number | undefined + + return b ?? 0; +>b ?? 0 : number +>b : number | undefined +>0 : 0 + +}; + +export function f5({ a: { b } = {} }: { a?: { b?: number } }) { +>f5 : ({ a: { b } }: { a?: { b?: number; }; }) => number | undefined +>a : any +>b : number | undefined +>{} : {} +>a : { b?: number; } | undefined +>b : number | undefined + + const c: string = b; +>c : string +>b : number | undefined + + return b; +>b : number | undefined +} + +export const f6: (input?: { a?: number }) => void = ({ a, b } = {}) => { +>f6 : (input?: { a?: number; }) => void +>input : { a?: number; } | undefined +>a : number | undefined +>({ a, b } = {}) => { const c: string = a;} : ({ a, b }?: { a?: number; } | undefined) => void +>a : number | undefined +>b : any +>{} : {} + + const c: string = a; +>c : string +>a : number | undefined + +}; + +export const f7: (input?: [{ a?: number }?]) => void = ([{ a } = {}] = []) => { +>f7 : (input?: [{ a?: number; }?]) => void +>input : [({ a?: number; } | undefined)?] | undefined +>a : number | undefined +>([{ a } = {}] = []) => { const b: string = a;} : ([{ a }]?: [({ a?: number; } | undefined)?] | undefined) => void +>a : number | undefined +>{} : {} +>[] : [] + + const b: string = a; +>b : string +>a : number | undefined + +}; + +export const f8: (input?: { [key: string]: number }) => void = ({ a } = {}) => { +>f8 : (input?: { [key: string]: number; }) => void +>input : { [key: string]: number; } | undefined +>key : string +>({ a } = {}) => { const b: string = a;} : ({ a }?: { [key: string]: number; } | undefined) => void +>a : number +>{} : {} + + const b: string = a; +>b : string +>a : number + +}; + +export const f9: (input?: any) => void = ({ a: { b } = {} } = {}) => { +>f9 : (input?: any) => void +>input : any +>({ a: { b } = {} } = {}) => { b;} : ({ a: { b } }?: any) => void +>a : any +>b : any +>{} : {} +>{} : {} + + b; +>b : any + +}; + +declare function f10(value: T, callback: (input?: Partial) => void): void; +>f10 : (value: T, callback: (input?: Partial) => void) => void +>value : T +>callback : (input?: Partial) => void +>input : Partial | undefined + +f10({ a: 1 }, ({ a } = {}) => { +>f10({ a: 1 }, ({ a } = {}) => { const b: string = a;}) : void +>f10 : (value: T, callback: (input?: Partial) => void) => void +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +>({ a } = {}) => { const b: string = a;} : ({ a }?: Partial<{ a: number; }> | undefined) => void +>a : number | undefined +>{} : {} + + const b: string = a; +>b : string +>a : number | undefined + +}); + +export const f11: (input?: { a?: number }[]) => void = ([...[{ a } = {}]] = []) => { +>f11 : (input?: { a?: number; }[]) => void +>input : { a?: number; }[] | undefined +>a : number | undefined +>([...[{ a } = {}]] = []) => { const b: string = a;} : ([...[{ a }]]?: { a?: number; }[] | undefined) => void +>a : number | undefined +>{} : {} +>[] : never[] + + const b: string = a; +>b : string +>a : number | undefined + +}; + +export const f12: (input: { a: boolean; b?: string }) => string = ({ a, b } = { a: false }) => `${a}${b}`; +>f12 : (input: { a: boolean; b?: string; }) => string +>input : { a: boolean; b?: string; } +>a : boolean +>b : string | undefined +>({ a, b } = { a: false }) => `${a}${b}` : ({ a, b }?: { a: boolean; b?: string; }) => string +>a : boolean +>b : string | undefined +>{ a: false } : { a: false; } +>a : false +>false : false +>`${a}${b}` : string +>a : boolean +>b : string | undefined + +export const f13 = (input: { a?: number; b?: string }[]) => input.map(({ a, b } = {}) => `${a}${b}`); +>f13 : (input: { a?: number; b?: string; }[]) => string[] +>(input: { a?: number; b?: string }[]) => input.map(({ a, b } = {}) => `${a}${b}`) : (input: { a?: number; b?: string; }[]) => string[] +>input : { a?: number; b?: string; }[] +>a : number | undefined +>b : string | undefined +>input.map(({ a, b } = {}) => `${a}${b}`) : string[] +>input.map : (callbackfn: (value: { a?: number; b?: string; }, index: number, array: { a?: number; b?: string; }[]) => U, thisArg?: any) => U[] +>input : { a?: number; b?: string; }[] +>map : (callbackfn: (value: { a?: number; b?: string; }, index: number, array: { a?: number; b?: string; }[]) => U, thisArg?: any) => U[] +>({ a, b } = {}) => `${a}${b}` : ({ a, b }?: { a?: number; b?: string; }) => string +>a : number | undefined +>b : string | undefined +>{} : {} +>`${a}${b}` : string +>a : number | undefined +>b : string | undefined + diff --git a/tsc/testdata/tests/cases/compiler/contextualDestructuredParameterDefault.ts b/tsc/testdata/tests/cases/compiler/contextualDestructuredParameterDefault.ts new file mode 100644 index 0000000000000..24d318116b926 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/contextualDestructuredParameterDefault.ts @@ -0,0 +1,31 @@ +// @strict: true +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @target: es2022 +// @lib: es2022,dom + +// Regression tests for https://github.com/microsoft/TypeScript/issues/65011 +// Contextual typing of a destructuring parameter with a default must reach binding elements. + +// @filename: /a.js +/** @typedef {{ signal?: AbortSignal }} Opts */ +/** @typedef {{ structure: (c: string, o?: Opts) => Promise }} Adapter */ +/** @returns {Adapter} */ +export function make() { + return { + async structure(c, { signal } = {}) { + signal?.throwIfAborted() + return [c] + }, + } +} + +/** @typedef {(n: number, o?: Opts) => Promise} Fn */ +export const p = /** @type {Fn} */ (async (n, { signal } = {}) => { + signal?.throwIfAborted() +}) + +// @filename: /b.ts +type O = { signal?: AbortSignal } +const f: (o?: O) => void = ({ signal } = {}) => { signal?.throwIfAborted() } diff --git a/tsc/testdata/tests/cases/compiler/objectBindingPatternDefaultMissingElements.ts b/tsc/testdata/tests/cases/compiler/objectBindingPatternDefaultMissingElements.ts index e0ea93281bfc9..6e100df41f23f 100644 --- a/tsc/testdata/tests/cases/compiler/objectBindingPatternDefaultMissingElements.ts +++ b/tsc/testdata/tests/cases/compiler/objectBindingPatternDefaultMissingElements.ts @@ -53,3 +53,55 @@ checkedObject({ required: "value" }); checkedArray(["value"]); typedObject({ required: "value" }); typedArray(["value"]); + +// Contextual typing must win over padding for `= {}` defaults (#64974 / #65011). +type F = (input?: { a?: string; b?: number }) => number; +export const f1: F = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); + +export const f2: (input: { a?: string; b?: number }) => number = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); + +declare function f3(callback: F): void; +f3(({ a, b } = {}) => { + const c: number = a; + const d: string = b; + return 0; +}); + +export const f4: (input?: { a?: { b?: number } }) => number = ({ a: { b } = {} } = {}) => { + const c: string = b; + return b ?? 0; +}; + +export function f5({ a: { b } = {} }: { a?: { b?: number } }) { + const c: string = b; + return b; +} + +export const f6: (input?: { a?: number }) => void = ({ a, b } = {}) => { + const c: string = a; +}; + +export const f7: (input?: [{ a?: number }?]) => void = ([{ a } = {}] = []) => { + const b: string = a; +}; + +export const f8: (input?: { [key: string]: number }) => void = ({ a } = {}) => { + const b: string = a; +}; + +export const f9: (input?: any) => void = ({ a: { b } = {} } = {}) => { + b; +}; + +declare function f10(value: T, callback: (input?: Partial) => void): void; +f10({ a: 1 }, ({ a } = {}) => { + const b: string = a; +}); + +export const f11: (input?: { a?: number }[]) => void = ([...[{ a } = {}]] = []) => { + const b: string = a; +}; + +export const f12: (input: { a: boolean; b?: string }) => string = ({ a, b } = { a: false }) => `${a}${b}`; + +export const f13 = (input: { a?: number; b?: string }[]) => input.map(({ a, b } = {}) => `${a}${b}`);