Skip to content

Commit 910ec1d

Browse files
committed
Control flow analysis for destructured rests
1 parent 3eb7b6a commit 910ec1d

File tree

6 files changed

+718
-22
lines changed

6 files changed

+718
-22
lines changed

src/compiler/checker.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,7 @@ export const enum CheckMode {
13391339
// e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`,
13401340
// we need to preserve generic types instead of substituting them for constraints
13411341
TypeOnly = 1 << 6, // Called from getTypeOfExpression, diagnostics may be omitted
1342+
SkipConstraintsSubstitution = 1 << 7,
13421343
}
13431344

13441345
/** @internal */
@@ -30365,21 +30366,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3036530366
return contextualType && !isGenericType(contextualType);
3036630367
}
3036730368

30368-
function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) {
30369-
if (isNoInferType(type)) {
30370-
type = (type as SubstitutionType).baseType;
30371-
}
30369+
function shouldSubstituteConstraints(type: Type, reference: Node, checkMode?: CheckMode) {
3037230370
// When the type of a reference is or contains an instantiable type with a union type constraint, and
3037330371
// when the reference is in a constraint position (where it is known we'll obtain the apparent type) or
3037430372
// has a contextual type containing no top-level instantiables (meaning constraints will determine
3037530373
// assignability), we substitute constraints for all instantiables in the type of the reference to give
3037630374
// control flow analysis an opportunity to narrow it further. For example, for a reference of a type
3037730375
// parameter type 'T extends string | undefined' with a contextual type 'string', we substitute
3037830376
// 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'.
30379-
const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) &&
30377+
return !(checkMode && checkMode & CheckMode.Inferential) &&
3038030378
someType(type, isGenericTypeWithUnionConstraint) &&
3038130379
(isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode));
30382-
return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type;
30380+
}
30381+
30382+
function getNarrowableTypeForReference(type: Type, reference: Node, checkMode = CheckMode.Normal) {
30383+
if (isNoInferType(type)) {
30384+
type = (type as SubstitutionType).baseType;
30385+
}
30386+
if (checkMode & CheckMode.SkipConstraintsSubstitution) {
30387+
return type;
30388+
}
30389+
return shouldSubstituteConstraints(type, reference, checkMode) ? mapType(type, getBaseConstraintOrType) : type;
3038330390
}
3038430391

3038530392
function isExportOrExportExpression(location: Node) {
@@ -30811,7 +30818,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3081130818
const type = getTypeOfSymbol(symbol);
3081230819
const declaration = symbol.valueDeclaration;
3081330820
if (declaration) {
30814-
// If we have a non-rest binding element with no initializer declared as a const variable or a const-like
30821+
// If we have a binding element with no initializer declared as a const variable or a const-like
3081530822
// parameter (a parameter for which there are no assignments in the function body), and if the parent type
3081630823
// for the destructuring is a union type, one or more of the binding elements may represent discriminant
3081730824
// properties, and we want the effects of conditional checks on such discriminants to affect the types of
@@ -30834,19 +30841,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3083430841
// the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference
3083530842
// as if it occurred in the specified location. We then recompute the narrowed binding element type by
3083630843
// destructuring from the narrowed parent type.
30837-
if (isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) {
30844+
if (isBindingElement(declaration) && !declaration.initializer && declaration.parent.elements.length >= 2) {
3083830845
const parent = declaration.parent.parent;
3083930846
const rootDeclaration = getRootDeclaration(parent);
3084030847
if (rootDeclaration.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(rootDeclaration) & NodeFlags.Constant || rootDeclaration.kind === SyntaxKind.Parameter) {
3084130848
const links = getNodeLinks(parent);
3084230849
if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) {
3084330850
links.flags |= NodeCheckFlags.InCheckIdentifier;
30844-
const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal);
30845-
const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType);
30851+
const parentType = getTypeForBindingElementParent(parent, shouldSubstituteConstraints(type, location) ? CheckMode.Normal : CheckMode.SkipConstraintsSubstitution);
30852+
const parentNarrowableType = parentType && getNarrowableTypeForReference(parentType, location);
30853+
3084630854
links.flags &= ~NodeCheckFlags.InCheckIdentifier;
30847-
if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) {
30855+
if (parentNarrowableType && parentNarrowableType.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) {
3084830856
const pattern = declaration.parent;
30849-
const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode);
30857+
const narrowedType = getFlowTypeOfReference(pattern, parentNarrowableType, parentNarrowableType, /*flowContainer*/ undefined, location.flowNode);
3085030858
if (narrowedType.flags & TypeFlags.Never) {
3085130859
return neverType;
3085230860
}

tests/baselines/reference/arrayDestructuringInSwitch1.symbols

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export function evaluate(expression: Expression): boolean {
3131

3232
case 'and': {
3333
return operands.every((child) => evaluate(child));
34-
>operands.every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
34+
>operands.every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
3535
>operands : Symbol(operands, Decl(arrayDestructuringInSwitch1.ts, 5, 20))
36-
>every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
36+
>every : Symbol(Array.every, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
3737
>child : Symbol(child, Decl(arrayDestructuringInSwitch1.ts, 8, 31))
3838
>evaluate : Symbol(evaluate, Decl(arrayDestructuringInSwitch1.ts, 1, 84))
3939
>child : Symbol(child, Decl(arrayDestructuringInSwitch1.ts, 8, 31))

tests/baselines/reference/arrayDestructuringInSwitch1.types

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ export function evaluate(expression: Expression): boolean {
4646
return operands.every((child) => evaluate(child));
4747
>operands.every((child) => evaluate(child)) : boolean
4848
> : ^^^^^^^
49-
>operands.every : { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; } | { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; }
50-
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^
51-
>operands : Expression[] | [Expression]
52-
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
53-
>every : { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; } | { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; }
54-
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^
49+
>operands.every : { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; }
50+
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^
51+
>operands : Expression[]
52+
> : ^^^^^^^^^^^^
53+
>every : { <S extends Expression>(predicate: (value: Expression, index: number, array: Expression[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: Expression, index: number, array: Expression[]) => unknown, thisArg?: any): boolean; }
54+
> : ^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^ ^ ^^^ ^^^ ^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^ ^^^
5555
>(child) => evaluate(child) : (child: Expression) => boolean
5656
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^
5757
>child : Expression
@@ -76,8 +76,8 @@ export function evaluate(expression: Expression): boolean {
7676
> : ^ ^^ ^^^^^
7777
>operands[0] : Expression
7878
> : ^^^^^^^^^^
79-
>operands : Expression[] | [Expression]
80-
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
79+
>operands : [Expression]
80+
> : ^^^^^^^^^^^^
8181
>0 : 0
8282
> : ^
8383
}

0 commit comments

Comments
 (0)