Skip to content

Commit 71556f6

Browse files
committed
🔨 Limit binding element/pattern type inference to literals (optional)
Signed-off-by: Babak K. Shandiz <[email protected]> diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index abf3e9d..21eefd8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9440,16 +9440,32 @@ namespace ts { // Return the type implied by a binding pattern element. This is the type of the initializer of the element if // one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding // pattern. Otherwise, it is the type any. - function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type { + function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean, skipNonLiteralInitializer?: boolean): Type { if (element.initializer) { // The type implied by a binding pattern is independent of context, so we check the initializer with no // contextual type or, if the element itself is a binding pattern, with the type implied by that binding // pattern. - const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType; - return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType))); + const contextualType = isBindingPattern(element.name) + ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false, skipNonLiteralInitializer) + : unknownType; + + // (microsoft#49989) + // If `skipNonLiteralInitializer` is not set, in cases where the intitializer is an identifier pointing + // to a sibling symbol in the same declaration, a false circular relationship will be concluded. For + // example, take the declarations below: + // + // const [a, b = a] = [1]; + // const {a, b = a} = {a: 1}; + // + // Here when the `element` is the second binding element, `b = a`, the initializer is `a` which itself + // is defined within the same binding pattern. + const type = skipNonLiteralInitializer && !isLiteralExpression(element.initializer) + ? contextualType + : checkDeclarationInitializer(element, CheckMode.Normal, contextualType); + return addOptionality(widenTypeInferredFromInitializer(element, type)); } if (isBindingPattern(element.name)) { - return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); + return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors, skipNonLiteralInitializer); } if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) { reportImplicitAny(element, anyType); @@ -9458,7 +9474,7 @@ namespace ts { } // Return the type implied by an object binding pattern - function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { + function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean, skipNonLiteralInitializer: boolean): Type { const members = createSymbolTable(); let stringIndexInfo: IndexInfo | undefined; let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; @@ -9478,7 +9494,7 @@ namespace ts { const text = getPropertyNameFromType(exprType); const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); const symbol = createSymbol(flags, text); - symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); + symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors, skipNonLiteralInitializer); symbol.bindingElement = e; members.set(symbol.escapedName, symbol); }); @@ -9492,14 +9508,14 @@ namespace ts { } // Return the type implied by an array binding pattern - function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { + function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean, skipNonLiteralInitializer: boolean): Type { const elements = pattern.elements; const lastElement = lastOrUndefined(elements); const restElement = lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken ? lastElement : undefined; if (elements.length === 0 || elements.length === 1 && restElement) { return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType; } - const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); + const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors, skipNonLiteralInitializer)); const minLength = findLastIndex(elements, e => !(e === restElement || isOmittedExpression(e) || hasDefaultValue(e)), elements.length - 1) + 1; const elementFlags = map(elements, (e, i) => e === restElement ? ElementFlags.Rest : i >= minLength ? ElementFlags.Optional : ElementFlags.Required); let result = createTupleType(elementTypes, elementFlags) as TypeReference; @@ -9518,10 +9534,10 @@ namespace ts { // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of // the parameter. - function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type { + function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false, skipNonLiteralInitializer = false): Type { return pattern.kind === SyntaxKind.ObjectBindingPattern - ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors) - : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors); + ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors, skipNonLiteralInitializer) + : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors, skipNonLiteralInitializer); } // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type @@ -26753,7 +26769,7 @@ namespace ts { return result; } if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name) && declaration.name.elements.length > 0) { - return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); + return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false, /*skipNonLiteralInitializer*/ true); } } return undefined;
1 parent 966e732 commit 71556f6

File tree

1 file changed

+28
-12
lines changed

1 file changed

+28
-12
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9440,16 +9440,32 @@ namespace ts {
94409440
// Return the type implied by a binding pattern element. This is the type of the initializer of the element if
94419441
// one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding
94429442
// pattern. Otherwise, it is the type any.
9443-
function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type {
9443+
function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean, skipNonLiteralInitializer?: boolean): Type {
94449444
if (element.initializer) {
94459445
// The type implied by a binding pattern is independent of context, so we check the initializer with no
94469446
// contextual type or, if the element itself is a binding pattern, with the type implied by that binding
94479447
// pattern.
9448-
const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType;
9449-
return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType)));
9448+
const contextualType = isBindingPattern(element.name)
9449+
? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false, skipNonLiteralInitializer)
9450+
: unknownType;
9451+
9452+
// (#49989)
9453+
// If `skipNonLiteralInitializer` is not set, in cases where the intitializer is an identifier pointing
9454+
// to a sibling symbol in the same declaration, a false circular relationship will be concluded. For
9455+
// example, take the declarations below:
9456+
//
9457+
// const [a, b = a] = [1];
9458+
// const {a, b = a} = {a: 1};
9459+
//
9460+
// Here when the `element` is the second binding element, `b = a`, the initializer is `a` which itself
9461+
// is defined within the same binding pattern.
9462+
const type = skipNonLiteralInitializer && !isLiteralExpression(element.initializer)
9463+
? contextualType
9464+
: checkDeclarationInitializer(element, CheckMode.Normal, contextualType);
9465+
return addOptionality(widenTypeInferredFromInitializer(element, type));
94509466
}
94519467
if (isBindingPattern(element.name)) {
9452-
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
9468+
return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors, skipNonLiteralInitializer);
94539469
}
94549470
if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) {
94559471
reportImplicitAny(element, anyType);
@@ -9458,7 +9474,7 @@ namespace ts {
94589474
}
94599475

94609476
// Return the type implied by an object binding pattern
9461-
function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type {
9477+
function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean, skipNonLiteralInitializer: boolean): Type {
94629478
const members = createSymbolTable();
94639479
let stringIndexInfo: IndexInfo | undefined;
94649480
let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
@@ -9478,7 +9494,7 @@ namespace ts {
94789494
const text = getPropertyNameFromType(exprType);
94799495
const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0);
94809496
const symbol = createSymbol(flags, text);
9481-
symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors);
9497+
symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors, skipNonLiteralInitializer);
94829498
symbol.bindingElement = e;
94839499
members.set(symbol.escapedName, symbol);
94849500
});
@@ -9492,14 +9508,14 @@ namespace ts {
94929508
}
94939509

94949510
// Return the type implied by an array binding pattern
9495-
function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type {
9511+
function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean, skipNonLiteralInitializer: boolean): Type {
94969512
const elements = pattern.elements;
94979513
const lastElement = lastOrUndefined(elements);
94989514
const restElement = lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken ? lastElement : undefined;
94999515
if (elements.length === 0 || elements.length === 1 && restElement) {
95009516
return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType;
95019517
}
9502-
const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors));
9518+
const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors, skipNonLiteralInitializer));
95039519
const minLength = findLastIndex(elements, e => !(e === restElement || isOmittedExpression(e) || hasDefaultValue(e)), elements.length - 1) + 1;
95049520
const elementFlags = map(elements, (e, i) => e === restElement ? ElementFlags.Rest : i >= minLength ? ElementFlags.Optional : ElementFlags.Required);
95059521
let result = createTupleType(elementTypes, elementFlags) as TypeReference;
@@ -9518,10 +9534,10 @@ namespace ts {
95189534
// used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring
95199535
// parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of
95209536
// the parameter.
9521-
function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type {
9537+
function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false, skipNonLiteralInitializer = false): Type {
95229538
return pattern.kind === SyntaxKind.ObjectBindingPattern
9523-
? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors)
9524-
: getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors);
9539+
? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors, skipNonLiteralInitializer)
9540+
: getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors, skipNonLiteralInitializer);
95259541
}
95269542

95279543
// Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type
@@ -26753,7 +26769,7 @@ namespace ts {
2675326769
return result;
2675426770
}
2675526771
if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name) && declaration.name.elements.length > 0) {
26756-
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false);
26772+
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false, /*skipNonLiteralInitializer*/ true);
2675726773
}
2675826774
}
2675926775
return undefined;

0 commit comments

Comments
 (0)