diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 5a5a155797c24..98d7df0c17440 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -17231,15 +17231,19 @@ func (c *Checker) checkDeclarationInitializer(declaration *ast.Node, checkMode C t = c.checkExpressionCachedEx(initializer, checkMode) } } - if ast.IsParameterDeclaration(ast.GetRootDeclaration(declaration)) { + root := ast.GetRootDeclaration(declaration) + if ast.IsParameterDeclaration(root) { name := declaration.Name() switch name.Kind { case ast.KindObjectBindingPattern: - if isObjectLiteralType(t) { + // Padding invents any-typed missing properties and would override a + // contextual parameter type (#64974). + if isObjectLiteralType(t) && c.getContextualTypeForVariableLikeDeclaration(root, ContextFlagsNone) == nil { return c.padObjectLiteralType(t, name) } case ast.KindArrayBindingPattern: - if isTupleType(t) { + // Same for tuple padding on `= []` defaults (#65069). + if isTupleType(t) && c.getContextualTypeForVariableLikeDeclaration(root, ContextFlagsNone) == nil { return c.padTupleType(t, name) } } diff --git a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.errors.txt b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.errors.txt index 823bd410ef0cb..26e3a92881b53 100644 --- a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.errors.txt +++ b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.errors.txt @@ -3,6 +3,14 @@ /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(39,11): error TS2322: Type 'string | undefined' is not assignable to type 'number'. + Type 'undefined' is not assignable to type 'number'. +/main.ts(40,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +/main.ts(45,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. +/main.ts(49,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 +40,7 @@ optional = false, ] = []) => {}; -==== /main.ts (3 errors) ==== +==== /main.ts (7 errors) ==== import { checkedArray, checkedObject } from "./checked"; import { uncheckedArray, uncheckedObject } from "./unchecked"; @@ -65,4 +73,40 @@ checkedArray(["value"]); typedObject({ required: "value" }); typedArray(["value"]); + + // Contextual typing must win over padding for parameter defaults (#64974 / #65069). + type Obj = (input?: { a?: string; b?: number }) => number; + export const obj1: Obj = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); + + type Arr = (input?: [string?, number?]) => number; + export const arr1: Arr = ([a, b] = []) => (a ? 1 : 0) + (b ?? 0); + + export const arr2: (input: [string?, number?]) => number = ([a, b] = []) => (a ? 1 : 0) + (b ?? 0); + + declare function takeArr(callback: Arr): void; + takeArr(([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 arr3: (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 arr4: (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'. + }; \ 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..7ce3026f92b3c 100644 --- a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.symbols +++ b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.symbols @@ -114,3 +114,83 @@ typedObject({ required: "value" }); typedArray(["value"]); >typedArray : Symbol(typedArray, Decl(main.ts, 8, 12)) +// Contextual typing must win over padding for parameter defaults (#64974 / #65069). +type Obj = (input?: { a?: string; b?: number }) => number; +>Obj : Symbol(Obj, Decl(main.ts, 25, 22)) +>input : Symbol(input, Decl(main.ts, 28, 12)) +>a : Symbol(a, Decl(main.ts, 28, 21)) +>b : Symbol(b, Decl(main.ts, 28, 33)) + +export const obj1: Obj = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); +>obj1 : Symbol(obj1, Decl(main.ts, 29, 12)) +>Obj : Symbol(Obj, Decl(main.ts, 25, 22)) +>a : Symbol(a, Decl(main.ts, 29, 27)) +>b : Symbol(b, Decl(main.ts, 29, 30)) +>a : Symbol(a, Decl(main.ts, 29, 27)) +>b : Symbol(b, Decl(main.ts, 29, 30)) + +type Arr = (input?: [string?, number?]) => number; +>Arr : Symbol(Arr, Decl(main.ts, 29, 67)) +>input : Symbol(input, Decl(main.ts, 31, 12)) + +export const arr1: Arr = ([a, b] = []) => (a ? 1 : 0) + (b ?? 0); +>arr1 : Symbol(arr1, Decl(main.ts, 32, 12)) +>Arr : Symbol(Arr, Decl(main.ts, 29, 67)) +>a : Symbol(a, Decl(main.ts, 32, 27)) +>b : Symbol(b, Decl(main.ts, 32, 29)) +>a : Symbol(a, Decl(main.ts, 32, 27)) +>b : Symbol(b, Decl(main.ts, 32, 29)) + +export const arr2: (input: [string?, number?]) => number = ([a, b] = []) => (a ? 1 : 0) + (b ?? 0); +>arr2 : Symbol(arr2, Decl(main.ts, 34, 12)) +>input : Symbol(input, Decl(main.ts, 34, 20)) +>a : Symbol(a, Decl(main.ts, 34, 61)) +>b : Symbol(b, Decl(main.ts, 34, 63)) +>a : Symbol(a, Decl(main.ts, 34, 61)) +>b : Symbol(b, Decl(main.ts, 34, 63)) + +declare function takeArr(callback: Arr): void; +>takeArr : Symbol(takeArr, Decl(main.ts, 34, 99)) +>callback : Symbol(callback, Decl(main.ts, 36, 25)) +>Arr : Symbol(Arr, Decl(main.ts, 29, 67)) + +takeArr(([a, b] = []) => { +>takeArr : Symbol(takeArr, Decl(main.ts, 34, 99)) +>a : Symbol(a, Decl(main.ts, 37, 10)) +>b : Symbol(b, Decl(main.ts, 37, 12)) + + const c: number = a; +>c : Symbol(c, Decl(main.ts, 38, 9)) +>a : Symbol(a, Decl(main.ts, 37, 10)) + + const d: string = b; +>d : Symbol(d, Decl(main.ts, 39, 9)) +>b : Symbol(b, Decl(main.ts, 37, 12)) + + return 0; +}); + +export const arr3: (input?: [{ a?: number }?]) => void = ([{ a } = {}] = []) => { +>arr3 : Symbol(arr3, Decl(main.ts, 43, 12)) +>input : Symbol(input, Decl(main.ts, 43, 20)) +>a : Symbol(a, Decl(main.ts, 43, 30)) +>a : Symbol(a, Decl(main.ts, 43, 60)) + + const b: string = a; +>b : Symbol(b, Decl(main.ts, 44, 9)) +>a : Symbol(a, Decl(main.ts, 43, 60)) + +}; + +export const arr4: (input?: { a?: number }[]) => void = ([...[{ a } = {}]] = []) => { +>arr4 : Symbol(arr4, Decl(main.ts, 47, 12)) +>input : Symbol(input, Decl(main.ts, 47, 20)) +>a : Symbol(a, Decl(main.ts, 47, 29)) +>a : Symbol(a, Decl(main.ts, 47, 63)) + + const b: string = a; +>b : Symbol(b, Decl(main.ts, 48, 9)) +>a : Symbol(a, Decl(main.ts, 47, 63)) + +}; + diff --git a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.types b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.types index 1791cbfaebf14..c7d38dadddcfa 100644 --- a/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.types +++ b/tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.types @@ -153,3 +153,121 @@ typedArray(["value"]); >["value"] : [string] >"value" : "value" +// Contextual typing must win over padding for parameter defaults (#64974 / #65069). +type Obj = (input?: { a?: string; b?: number }) => number; +>Obj : Obj +>input : { a?: string; b?: number; } | undefined +>a : string | undefined +>b : number | undefined + +export const obj1: Obj = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); +>obj1 : Obj +>({ 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 + +type Arr = (input?: [string?, number?]) => number; +>Arr : Arr +>input : [(string | undefined)?, (number | undefined)?] | undefined + +export const arr1: Arr = ([a, b] = []) => (a ? 1 : 0) + (b ?? 0); +>arr1 : Arr +>([a, b] = []) => (a ? 1 : 0) + (b ?? 0) : ([a, b]?: [(string | undefined)?, (number | undefined)?] | 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 arr2: (input: [string?, number?]) => number = ([a, b] = []) => (a ? 1 : 0) + (b ?? 0); +>arr2 : (input: [string?, number?]) => number +>input : [(string | undefined)?, (number | undefined)?] +>([a, b] = []) => (a ? 1 : 0) + (b ?? 0) : ([a, b]?: [(string | undefined)?, (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 + +declare function takeArr(callback: Arr): void; +>takeArr : (callback: Arr) => void +>callback : Arr + +takeArr(([a, b] = []) => { +>takeArr(([a, b] = []) => { const c: number = a; const d: string = b; return 0;}) : void +>takeArr : (callback: Arr) => void +>([a, b] = []) => { const c: number = a; const d: string = b; return 0;} : ([a, b]?: [(string | undefined)?, (number | undefined)?] | 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 arr3: (input?: [{ a?: number }?]) => void = ([{ a } = {}] = []) => { +>arr3 : (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 arr4: (input?: { a?: number }[]) => void = ([...[{ a } = {}]] = []) => { +>arr4 : (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 + +}; + diff --git a/tsc/testdata/tests/cases/compiler/objectBindingPatternDefaultMissingElements.ts b/tsc/testdata/tests/cases/compiler/objectBindingPatternDefaultMissingElements.ts index e0ea93281bfc9..95a30bd8f754c 100644 --- a/tsc/testdata/tests/cases/compiler/objectBindingPatternDefaultMissingElements.ts +++ b/tsc/testdata/tests/cases/compiler/objectBindingPatternDefaultMissingElements.ts @@ -53,3 +53,27 @@ checkedObject({ required: "value" }); checkedArray(["value"]); typedObject({ required: "value" }); typedArray(["value"]); + +// Contextual typing must win over padding for parameter defaults (#64974 / #65069). +type Obj = (input?: { a?: string; b?: number }) => number; +export const obj1: Obj = ({ a, b } = {}) => (a ? 1 : 0) + (b ?? 0); + +type Arr = (input?: [string?, number?]) => number; +export const arr1: Arr = ([a, b] = []) => (a ? 1 : 0) + (b ?? 0); + +export const arr2: (input: [string?, number?]) => number = ([a, b] = []) => (a ? 1 : 0) + (b ?? 0); + +declare function takeArr(callback: Arr): void; +takeArr(([a, b] = []) => { + const c: number = a; + const d: string = b; + return 0; +}); + +export const arr3: (input?: [{ a?: number }?]) => void = ([{ a } = {}] = []) => { + const b: string = a; +}; + +export const arr4: (input?: { a?: number }[]) => void = ([...[{ a } = {}]] = []) => { + const b: string = a; +};