Skip to content

Commit 42826db

Browse files
committed
Do not pad binding elements that the parameter's type already types
padObjectLiteralType pads every binding element missing from a parameter's default object literal with the element's own type, which is implicitly any when the element has no initializer. For an annotated or contextually typed parameter that replaces the type the annotation or the contextual signature gives the element, and reports TS7031 on an element that has a type: type Options = (options?: { recursive?: boolean }) => void; const f: Options = ({ recursive } = {}) => {}; // ~~~~~~~~~ TS7031, but recursive is boolean | undefined Resolve the type the pattern destructures from the annotation or the contextual signature, through nested patterns, and leave out of the padding each element that type has. Elements it does not have are padded and reported as before.
1 parent d61a7d2 commit 42826db

5 files changed

Lines changed: 407 additions & 1 deletion

File tree

‎tsc/internal/checker/checker.go‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17199,13 +17199,19 @@ func (c *Checker) checkDeclarationInitializer(declaration *ast.Node, checkMode C
1719917199
}
1720017200

1720117201
func (c *Checker) padObjectLiteralType(t *Type, pattern *ast.Node) *Type {
17202+
// An element that the parameter's annotation or contextual type already types is not padded:
17203+
// padding would replace that type with the element's own (implicitly any) type.
17204+
declaredType := c.getDeclaredTypeOfParameterBindingPattern(pattern)
1720217205
var missingElements []*ast.Node
1720317206
for _, e := range pattern.Elements() {
1720417207
if hasDotDotDotToken(e) {
1720517208
continue
1720617209
}
1720717210
name := c.getPropertyNameFromBindingElement(e)
1720817211
if name != ast.InternalSymbolNameMissing && c.getPropertyOfType(t, name) == nil {
17212+
if declaredType != nil && c.getTypeOfPropertyOfType(declaredType, name) != nil {
17213+
continue
17214+
}
1720917215
missingElements = append(missingElements, e)
1721017216
}
1721117217
}
@@ -17226,6 +17232,36 @@ func (c *Checker) padObjectLiteralType(t *Type, pattern *ast.Node) *Type {
1722617232
return result
1722717233
}
1722817234

17235+
// getDeclaredTypeOfParameterBindingPattern returns the type that an object binding pattern of a
17236+
// parameter destructures when the parameter's type annotation or its contextual signature supplies
17237+
// one. It never consults an initializer, so it is safe to call while checking one.
17238+
func (c *Checker) getDeclaredTypeOfParameterBindingPattern(pattern *ast.Node) *Type {
17239+
parent := pattern.Parent
17240+
var t *Type
17241+
switch {
17242+
case ast.IsBindingElement(parent):
17243+
if !ast.IsObjectBindingPattern(parent.Parent) {
17244+
return nil
17245+
}
17246+
outer := c.getDeclaredTypeOfParameterBindingPattern(parent.Parent)
17247+
name := c.getPropertyNameFromBindingElement(parent)
17248+
if outer == nil || name == ast.InternalSymbolNameMissing {
17249+
return nil
17250+
}
17251+
t = c.getTypeOfPropertyOfType(outer, name)
17252+
case ast.IsParameterDeclaration(parent):
17253+
if parent.Type() != nil {
17254+
t = c.getTypeFromTypeNode(parent.Type())
17255+
} else {
17256+
t = c.getContextuallyTypedParameterType(parent)
17257+
}
17258+
}
17259+
if t == nil {
17260+
return nil
17261+
}
17262+
return c.GetNonNullableType(t)
17263+
}
17264+
1722917265
func (c *Checker) getPropertyNameFromBindingElement(e *ast.Node) string {
1723017266
exprType := c.getLiteralTypeFromPropertyName(e.PropertyNameOrName())
1723117267
if isTypeUsableAsPropertyName(exprType) {

‎tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.errors.txt‎

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@
33
/main.ts(5,5): error TS7031: Binding element 'required' implicitly has an 'any' type.
44
/main.ts(10,5): error TS7031: Binding element 'required' implicitly has an 'any' type.
55
/main.ts(15,5): error TS7031: Binding element 'required' implicitly has an 'any' type.
6+
/main.ts(31,11): error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
7+
Type 'undefined' is not assignable to type 'string'.
8+
/main.ts(36,11): error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
9+
Type 'undefined' is not assignable to type 'string'.
10+
/main.ts(44,34): error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
11+
Type 'undefined' is not assignable to type 'string'.
12+
/main.ts(44,45): error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
13+
Type 'undefined' is not assignable to type 'string'.
14+
/main.ts(49,11): error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
15+
Type 'undefined' is not assignable to type 'string'.
16+
/main.ts(53,84): error TS2339: Property 'unknown' does not exist on type '{ known?: number | undefined; }'.
17+
/main.ts(53,84): error TS7031: Binding element 'unknown' implicitly has an 'any' type.
18+
/main.ts(54,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'.
19+
Type 'undefined' is not assignable to type 'string'.
20+
/main.ts(62,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'.
21+
Type 'undefined' is not assignable to type 'string'.
22+
/main.ts(65,11): error TS2322: Type 'number | undefined' is not assignable to type 'string'.
23+
Type 'undefined' is not assignable to type 'string'.
624

725

826
==== /unchecked.js (0 errors) ====
@@ -32,7 +50,7 @@
3250
optional = false,
3351
] = []) => {};
3452

35-
==== /main.ts (3 errors) ====
53+
==== /main.ts (13 errors) ====
3654
import { checkedArray, checkedObject } from "./checked";
3755
import { uncheckedArray, uncheckedObject } from "./unchecked";
3856

@@ -65,4 +83,72 @@
6583
checkedArray(["value"]);
6684
typedObject({ required: "value" });
6785
typedArray(["value"]);
86+
87+
// Contextually typed parameters: the contextual signature types the missing elements.
88+
type Options = (options?: { recursive?: boolean }) => void;
89+
export const contextualFunction: Options = ({ recursive } = {}) => {
90+
const flag: string = recursive; // error: boolean | undefined
91+
~~~~
92+
!!! error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
93+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
94+
};
95+
96+
declare function takesCallback(cb: (options?: { deep?: boolean }) => void): void;
97+
takesCallback(({ deep } = {}) => {
98+
const flag: string = deep; // error: boolean | undefined
99+
~~~~
100+
!!! error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
101+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
102+
});
103+
104+
interface Driver {
105+
rm(path: string, options?: { recursive?: boolean; force?: boolean }): void;
106+
}
107+
export const driver: Driver = {
108+
rm: (path, { recursive, force } = {}) => {
109+
const flags: string[] = [recursive, force]; // error: boolean | undefined
110+
~~~~~~~~~
111+
!!! error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
112+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
113+
~~~~~
114+
!!! error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
115+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
116+
},
117+
};
118+
119+
export const contextualNested: (options?: { inner?: { deep?: boolean } }) => void = ({ inner: { deep } = {} } = {}) => {
120+
const flag: string = deep; // error: boolean | undefined
121+
~~~~
122+
!!! error TS2322: Type 'boolean | undefined' is not assignable to type 'string'.
123+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
124+
};
125+
126+
// Only the elements the contextual type has are typed by it; the rest stay implicitly any.
127+
export const contextualPartial: (options?: { known?: number }) => void = ({ known, unknown } = {}) => {
128+
~~~~~~~
129+
!!! error TS2339: Property 'unknown' does not exist on type '{ known?: number | undefined; }'.
130+
~~~~~~~
131+
!!! error TS7031: Binding element 'unknown' implicitly has an 'any' type.
132+
const value: string = known; // error: number | undefined
133+
~~~~~
134+
!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'.
135+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
136+
};
137+
138+
// Annotated parameters: the annotation types the missing elements of a nested pattern.
139+
interface Outer {
140+
inner?: { b?: number };
141+
}
142+
export function annotatedNested({ inner: { b } = {} }: Outer) {
143+
const value: string = b; // error: number | undefined
144+
~~~~~
145+
!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'.
146+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
147+
}
148+
export function annotatedNestedWithDefault({ inner: { b } = {} }: Outer = {}) {
149+
const value: string = b; // error: number | undefined
150+
~~~~~
151+
!!! error TS2322: Type 'number | undefined' is not assignable to type 'string'.
152+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
153+
}
68154

‎tsc/testdata/baselines/reference/compiler/objectBindingPatternDefaultMissingElements.symbols‎

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,120 @@ typedObject({ required: "value" });
114114
typedArray(["value"]);
115115
>typedArray : Symbol(typedArray, Decl(main.ts, 8, 12))
116116

117+
// Contextually typed parameters: the contextual signature types the missing elements.
118+
type Options = (options?: { recursive?: boolean }) => void;
119+
>Options : Symbol(Options, Decl(main.ts, 25, 22))
120+
>options : Symbol(options, Decl(main.ts, 28, 16))
121+
>recursive : Symbol(recursive, Decl(main.ts, 28, 27))
122+
123+
export const contextualFunction: Options = ({ recursive } = {}) => {
124+
>contextualFunction : Symbol(contextualFunction, Decl(main.ts, 29, 12))
125+
>Options : Symbol(Options, Decl(main.ts, 25, 22))
126+
>recursive : Symbol(recursive, Decl(main.ts, 29, 45))
127+
128+
const flag: string = recursive; // error: boolean | undefined
129+
>flag : Symbol(flag, Decl(main.ts, 30, 9))
130+
>recursive : Symbol(recursive, Decl(main.ts, 29, 45))
131+
132+
};
133+
134+
declare function takesCallback(cb: (options?: { deep?: boolean }) => void): void;
135+
>takesCallback : Symbol(takesCallback, Decl(main.ts, 31, 2))
136+
>cb : Symbol(cb, Decl(main.ts, 33, 31))
137+
>options : Symbol(options, Decl(main.ts, 33, 36))
138+
>deep : Symbol(deep, Decl(main.ts, 33, 47))
139+
140+
takesCallback(({ deep } = {}) => {
141+
>takesCallback : Symbol(takesCallback, Decl(main.ts, 31, 2))
142+
>deep : Symbol(deep, Decl(main.ts, 34, 16))
143+
144+
const flag: string = deep; // error: boolean | undefined
145+
>flag : Symbol(flag, Decl(main.ts, 35, 9))
146+
>deep : Symbol(deep, Decl(main.ts, 34, 16))
147+
148+
});
149+
150+
interface Driver {
151+
>Driver : Symbol(Driver, Decl(main.ts, 36, 3))
152+
153+
rm(path: string, options?: { recursive?: boolean; force?: boolean }): void;
154+
>rm : Symbol(Driver.rm, Decl(main.ts, 38, 18))
155+
>path : Symbol(path, Decl(main.ts, 39, 7))
156+
>options : Symbol(options, Decl(main.ts, 39, 20))
157+
>recursive : Symbol(recursive, Decl(main.ts, 39, 32))
158+
>force : Symbol(force, Decl(main.ts, 39, 53))
159+
}
160+
export const driver: Driver = {
161+
>driver : Symbol(driver, Decl(main.ts, 41, 12))
162+
>Driver : Symbol(Driver, Decl(main.ts, 36, 3))
163+
164+
rm: (path, { recursive, force } = {}) => {
165+
>rm : Symbol(rm, Decl(main.ts, 41, 31))
166+
>path : Symbol(path, Decl(main.ts, 42, 9))
167+
>recursive : Symbol(recursive, Decl(main.ts, 42, 16))
168+
>force : Symbol(force, Decl(main.ts, 42, 27))
169+
170+
const flags: string[] = [recursive, force]; // error: boolean | undefined
171+
>flags : Symbol(flags, Decl(main.ts, 43, 13))
172+
>recursive : Symbol(recursive, Decl(main.ts, 42, 16))
173+
>force : Symbol(force, Decl(main.ts, 42, 27))
174+
175+
},
176+
};
177+
178+
export const contextualNested: (options?: { inner?: { deep?: boolean } }) => void = ({ inner: { deep } = {} } = {}) => {
179+
>contextualNested : Symbol(contextualNested, Decl(main.ts, 47, 12))
180+
>options : Symbol(options, Decl(main.ts, 47, 32))
181+
>inner : Symbol(inner, Decl(main.ts, 47, 43))
182+
>deep : Symbol(deep, Decl(main.ts, 47, 53))
183+
>deep : Symbol(deep, Decl(main.ts, 47, 95))
184+
185+
const flag: string = deep; // error: boolean | undefined
186+
>flag : Symbol(flag, Decl(main.ts, 48, 9))
187+
>deep : Symbol(deep, Decl(main.ts, 47, 95))
188+
189+
};
190+
191+
// Only the elements the contextual type has are typed by it; the rest stay implicitly any.
192+
export const contextualPartial: (options?: { known?: number }) => void = ({ known, unknown } = {}) => {
193+
>contextualPartial : Symbol(contextualPartial, Decl(main.ts, 52, 12))
194+
>options : Symbol(options, Decl(main.ts, 52, 33))
195+
>known : Symbol(known, Decl(main.ts, 52, 44))
196+
>known : Symbol(known, Decl(main.ts, 52, 75))
197+
>unknown : Symbol(unknown, Decl(main.ts, 52, 82))
198+
199+
const value: string = known; // error: number | undefined
200+
>value : Symbol(value, Decl(main.ts, 53, 9))
201+
>known : Symbol(known, Decl(main.ts, 52, 75))
202+
203+
};
204+
205+
// Annotated parameters: the annotation types the missing elements of a nested pattern.
206+
interface Outer {
207+
>Outer : Symbol(Outer, Decl(main.ts, 54, 2))
208+
209+
inner?: { b?: number };
210+
>inner : Symbol(Outer.inner, Decl(main.ts, 57, 17))
211+
>b : Symbol(b, Decl(main.ts, 58, 13))
212+
}
213+
export function annotatedNested({ inner: { b } = {} }: Outer) {
214+
>annotatedNested : Symbol(annotatedNested, Decl(main.ts, 59, 1))
215+
>inner : Symbol(Outer.inner, Decl(main.ts, 57, 17))
216+
>b : Symbol(b, Decl(main.ts, 60, 42))
217+
>Outer : Symbol(Outer, Decl(main.ts, 54, 2))
218+
219+
const value: string = b; // error: number | undefined
220+
>value : Symbol(value, Decl(main.ts, 61, 9))
221+
>b : Symbol(b, Decl(main.ts, 60, 42))
222+
}
223+
export function annotatedNestedWithDefault({ inner: { b } = {} }: Outer = {}) {
224+
>annotatedNestedWithDefault : Symbol(annotatedNestedWithDefault, Decl(main.ts, 62, 1))
225+
>inner : Symbol(Outer.inner, Decl(main.ts, 57, 17))
226+
>b : Symbol(b, Decl(main.ts, 63, 53))
227+
>Outer : Symbol(Outer, Decl(main.ts, 54, 2))
228+
229+
const value: string = b; // error: number | undefined
230+
>value : Symbol(value, Decl(main.ts, 64, 9))
231+
>b : Symbol(b, Decl(main.ts, 63, 53))
232+
}
233+

0 commit comments

Comments
 (0)