Skip to content

Commit 5788446

Browse files
authored
Use contextual parameter types over binding pattern initializer types (#28967)
* Use contextual parameter types over binding pattern initializer types * Remove unneeded check
1 parent fecbdb6 commit 5788446

8 files changed

+220
-29
lines changed

src/compiler/checker.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4849,7 +4849,7 @@ namespace ts {
48494849
if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined)) {
48504850
type = getTypeWithFacts(type, TypeFacts.NEUndefined);
48514851
}
4852-
return declaration.initializer && !getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration)) ?
4852+
return declaration.initializer && !getContextualTypeForVariableLikeDeclaration(walkUpBindingElementsAndPatterns(declaration)) ?
48534853
getUnionType([type, checkDeclarationInitializer(declaration)], UnionReduction.Subtype) :
48544854
type;
48554855
}
@@ -17009,6 +17009,32 @@ namespace ts {
1700917009
}
1701017010
}
1701117011

17012+
function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type | undefined {
17013+
const typeNode = getEffectiveTypeAnnotationNode(declaration);
17014+
if (typeNode) {
17015+
return getTypeFromTypeNode(typeNode);
17016+
}
17017+
switch (declaration.kind) {
17018+
case SyntaxKind.Parameter:
17019+
return getContextuallyTypedParameterType(declaration);
17020+
case SyntaxKind.BindingElement:
17021+
return getContextualTypeForBindingElement(declaration);
17022+
// By default, do nothing and return undefined - only parameters and binding elements have context implied by a parent
17023+
}
17024+
}
17025+
17026+
function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined {
17027+
const parentDeclaration = declaration.parent.parent;
17028+
const name = declaration.propertyName || declaration.name;
17029+
const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration);
17030+
if (parentType && !isBindingPattern(name)) {
17031+
const text = getTextOfPropertyName(name);
17032+
if (text !== undefined) {
17033+
return getTypeOfPropertyOfType(parentType, text);
17034+
}
17035+
}
17036+
}
17037+
1701217038
// In a variable, parameter or property declaration with a type annotation,
1701317039
// the contextual type of an initializer expression is the type of the variable, parameter or property.
1701417040
// Otherwise, in a parameter declaration of a contextually typed function expression,
@@ -17020,32 +17046,13 @@ namespace ts {
1702017046
function getContextualTypeForInitializerExpression(node: Expression): Type | undefined {
1702117047
const declaration = <VariableLikeDeclaration>node.parent;
1702217048
if (hasInitializer(declaration) && node === declaration.initializer) {
17023-
const typeNode = getEffectiveTypeAnnotationNode(declaration);
17024-
if (typeNode) {
17025-
return getTypeFromTypeNode(typeNode);
17026-
}
17027-
if (declaration.kind === SyntaxKind.Parameter) {
17028-
const type = getContextuallyTypedParameterType(declaration);
17029-
if (type) {
17030-
return type;
17031-
}
17049+
const result = getContextualTypeForVariableLikeDeclaration(declaration);
17050+
if (result) {
17051+
return result;
1703217052
}
17033-
if (isBindingPattern(declaration.name)) {
17053+
if (isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable
1703417054
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false);
1703517055
}
17036-
if (isBindingPattern(declaration.parent)) {
17037-
const parentDeclaration = declaration.parent.parent;
17038-
const name = (declaration as BindingElement).propertyName || declaration.name;
17039-
if (parentDeclaration.kind !== SyntaxKind.BindingElement) {
17040-
const parentTypeNode = getEffectiveTypeAnnotationNode(parentDeclaration);
17041-
if (parentTypeNode && !isBindingPattern(name)) {
17042-
const text = getTextOfPropertyName(name);
17043-
if (text) {
17044-
return getTypeOfPropertyOfType(getTypeFromTypeNode(parentTypeNode), text);
17045-
}
17046-
}
17047-
}
17048-
}
1704917056
}
1705017057
return undefined;
1705117058
}

tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ const [f, g = f, h = i, i = f] = [1]; // error for h = i
4545
>c : number
4646
>d : number
4747
>c : number
48-
>e : any
49-
>e : any
48+
>e : number
49+
>e : number
5050

5151
})([1]);
5252
>[1] : number[]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//// [destructuringInitializerContextualTypeFromContext.ts]
2+
interface SFC<P = {}> {
3+
(props: P & { children?: any }): any | null;
4+
}
5+
6+
interface Props {
7+
name: "Apollo" | "Artemis" | "Dionysus" | "Persephone";
8+
}
9+
10+
const Parent: SFC<Props> = ({
11+
children,
12+
name = "Artemis",
13+
...props
14+
}) => Child({name, ...props});
15+
16+
const Child: SFC<Props> = ({
17+
children,
18+
name = "Artemis",
19+
...props
20+
}) => `name: ${name} props: ${JSON.stringify(props)}`;
21+
22+
//// [destructuringInitializerContextualTypeFromContext.js]
23+
var __assign = (this && this.__assign) || function () {
24+
__assign = Object.assign || function(t) {
25+
for (var s, i = 1, n = arguments.length; i < n; i++) {
26+
s = arguments[i];
27+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
28+
t[p] = s[p];
29+
}
30+
return t;
31+
};
32+
return __assign.apply(this, arguments);
33+
};
34+
var __rest = (this && this.__rest) || function (s, e) {
35+
var t = {};
36+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
37+
t[p] = s[p];
38+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
39+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
40+
t[p[i]] = s[p[i]];
41+
return t;
42+
};
43+
var Parent = function (_a) {
44+
var children = _a.children, _b = _a.name, name = _b === void 0 ? "Artemis" : _b, props = __rest(_a, ["children", "name"]);
45+
return Child(__assign({ name: name }, props));
46+
};
47+
var Child = function (_a) {
48+
var children = _a.children, _b = _a.name, name = _b === void 0 ? "Artemis" : _b, props = __rest(_a, ["children", "name"]);
49+
return "name: " + name + " props: " + JSON.stringify(props);
50+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
=== tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts ===
2+
interface SFC<P = {}> {
3+
>SFC : Symbol(SFC, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 0))
4+
>P : Symbol(P, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 14))
5+
6+
(props: P & { children?: any }): any | null;
7+
>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 1, 5))
8+
>P : Symbol(P, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 14))
9+
>children : Symbol(children, Decl(destructuringInitializerContextualTypeFromContext.ts, 1, 17))
10+
}
11+
12+
interface Props {
13+
>Props : Symbol(Props, Decl(destructuringInitializerContextualTypeFromContext.ts, 2, 1))
14+
15+
name: "Apollo" | "Artemis" | "Dionysus" | "Persephone";
16+
>name : Symbol(Props.name, Decl(destructuringInitializerContextualTypeFromContext.ts, 4, 17))
17+
}
18+
19+
const Parent: SFC<Props> = ({
20+
>Parent : Symbol(Parent, Decl(destructuringInitializerContextualTypeFromContext.ts, 8, 5))
21+
>SFC : Symbol(SFC, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 0))
22+
>Props : Symbol(Props, Decl(destructuringInitializerContextualTypeFromContext.ts, 2, 1))
23+
24+
children,
25+
>children : Symbol(children, Decl(destructuringInitializerContextualTypeFromContext.ts, 8, 29))
26+
27+
name = "Artemis",
28+
>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 9, 13))
29+
30+
...props
31+
>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 10, 21))
32+
33+
}) => Child({name, ...props});
34+
>Child : Symbol(Child, Decl(destructuringInitializerContextualTypeFromContext.ts, 14, 5))
35+
>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 12, 13))
36+
>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 10, 21))
37+
38+
const Child: SFC<Props> = ({
39+
>Child : Symbol(Child, Decl(destructuringInitializerContextualTypeFromContext.ts, 14, 5))
40+
>SFC : Symbol(SFC, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 0))
41+
>Props : Symbol(Props, Decl(destructuringInitializerContextualTypeFromContext.ts, 2, 1))
42+
43+
children,
44+
>children : Symbol(children, Decl(destructuringInitializerContextualTypeFromContext.ts, 14, 28))
45+
46+
name = "Artemis",
47+
>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 15, 13))
48+
49+
...props
50+
>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 16, 21))
51+
52+
}) => `name: ${name} props: ${JSON.stringify(props)}`;
53+
>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 15, 13))
54+
>JSON.stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
55+
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
56+
>stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
57+
>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 16, 21))
58+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
=== tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts ===
2+
interface SFC<P = {}> {
3+
(props: P & { children?: any }): any | null;
4+
>props : P & { children?: any; }
5+
>children : any
6+
>null : null
7+
}
8+
9+
interface Props {
10+
name: "Apollo" | "Artemis" | "Dionysus" | "Persephone";
11+
>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone"
12+
}
13+
14+
const Parent: SFC<Props> = ({
15+
>Parent : SFC<Props>
16+
>({ children, name = "Artemis", ...props}) => Child({name, ...props}) : ({ children, name, ...props }: Props & { children?: any; }) => any
17+
18+
children,
19+
>children : any
20+
21+
name = "Artemis",
22+
>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone"
23+
>"Artemis" : "Artemis"
24+
25+
...props
26+
>props : {}
27+
28+
}) => Child({name, ...props});
29+
>Child({name, ...props}) : any
30+
>Child : SFC<Props>
31+
>{name, ...props} : { name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; }
32+
>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone"
33+
>props : {}
34+
35+
const Child: SFC<Props> = ({
36+
>Child : SFC<Props>
37+
>({ children, name = "Artemis", ...props}) => `name: ${name} props: ${JSON.stringify(props)}` : ({ children, name, ...props }: Props & { children?: any; }) => string
38+
39+
children,
40+
>children : any
41+
42+
name = "Artemis",
43+
>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone"
44+
>"Artemis" : "Artemis"
45+
46+
...props
47+
>props : {}
48+
49+
}) => `name: ${name} props: ${JSON.stringify(props)}`;
50+
>`name: ${name} props: ${JSON.stringify(props)}` : string
51+
>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone"
52+
>JSON.stringify(props) : string
53+
>JSON.stringify : { (value: any, replacer?: (key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (string | number)[], space?: string | number): string; }
54+
>JSON : JSON
55+
>stringify : { (value: any, replacer?: (key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (string | number)[], space?: string | number): string; }
56+
>props : {}
57+

tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number},
167167
>b : number
168168
>c : number
169169
>6 : 6
170-
>{b: 4, c: 5} : { b: number; c?: number; }
170+
>{b: 4, c: 5} : { b: number; c: number; }
171171
>b : number
172172
>4 : 4
173173
>c : number

tests/baselines/reference/sourceMapValidationDestructuringParameterNestedObjectBindingPatternDefaultValues.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function foo1(
5050
>"secondary" : "secondary"
5151

5252
} = { primary: "SomeSkill", secondary: "someSkill" }
53-
>{ primary: "SomeSkill", secondary: "someSkill" } : { primary?: string; secondary?: string; }
53+
>{ primary: "SomeSkill", secondary: "someSkill" } : { primary: string; secondary: string; }
5454
>primary : string
5555
>"SomeSkill" : "SomeSkill"
5656
>secondary : string
@@ -88,7 +88,7 @@ function foo2(
8888
>"secondary" : "secondary"
8989

9090
} = { primary: "SomeSkill", secondary: "someSkill" }
91-
>{ primary: "SomeSkill", secondary: "someSkill" } : { primary?: string; secondary?: string; }
91+
>{ primary: "SomeSkill", secondary: "someSkill" } : { primary: string; secondary: string; }
9292
>primary : string
9393
>"SomeSkill" : "SomeSkill"
9494
>secondary : string
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
interface SFC<P = {}> {
2+
(props: P & { children?: any }): any | null;
3+
}
4+
5+
interface Props {
6+
name: "Apollo" | "Artemis" | "Dionysus" | "Persephone";
7+
}
8+
9+
const Parent: SFC<Props> = ({
10+
children,
11+
name = "Artemis",
12+
...props
13+
}) => Child({name, ...props});
14+
15+
const Child: SFC<Props> = ({
16+
children,
17+
name = "Artemis",
18+
...props
19+
}) => `name: ${name} props: ${JSON.stringify(props)}`;

0 commit comments

Comments
 (0)