-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Using variable defined by array destructuring assignment in default value of variables defined afterwards. #49989
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> 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;
… sibling definitions Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com> diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index abf3e9d..793ffb3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9445,8 +9445,33 @@ namespace ts { // 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))); + if (isBindingPattern(element.name)) { + const contextualType = getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false); + return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType))); + } + + // (microsoft#49989) + // In cases where the intitializer is an identifier referencing a sibling symbol (i.e., one that is + // defined 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 (i.e., `b = a`) the initializer is `a` which + // itself is defined within the same binding pattern. + // + // So, we check the initializer expression for any references to sibling symbols and if any, then we'd + // conclude the binding element type as `unknownType` and thus skip further circulations in type + // checking. + const siblings = mapDefined(element.parent.elements, x => x !== element && isBindingElement(x) && isIdentifier(x.name) ? x : undefined); + const checkIsSiblingInvolved = (node: Node) => { + const declaration = isIdentifier(node) && getReferencedValueDeclaration(node); + return declaration && isBindingElement(declaration) && siblings.includes(declaration); + }; + const isSiblingElementInvolded = checkIsSiblingInvolved(element.initializer) || forEachChildRecursively(element.initializer, checkIsSiblingInvolved); + return isSiblingElementInvolded ? anyType + : addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, unknownType))); } if (isBindingPattern(element.name)) { return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors);
@RyanCavanaugh I've submitted a PR for this issue. Would you please check it later? |
Not sure if the issue reported this SO question with the code: declare const o: { a?: number, b?: number } | undefined
const { a, b = a } = o ?? {}; // error!
// ---> ~ 'a'
// implicitly has type 'any' because it does not have a type annotation and is
// referenced directly or indirectly in its own initializer.(7022) is the same issue or not. Waiting to see if any fix for this addresses it; if not, I might open a new issue for it (unless someone can point to a more appropriate existing issue) |
Indeed, looks like it. And since that got merged it's also visible in the nightly build. |
Bug Report
🔎 Search Terms
Array destructuring assignment default value 7022
🕗 Version & Regression Information
Exists in 3.3.3 - 4.8.0-beta
⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
Types of
a
andb
inferred asany
, or ifnoImplicitAny
enabled, shows error:'a' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.(7022)
🙂 Expected behavior
Types of both
a
andb
should be inferred asnumber
.The text was updated successfully, but these errors were encountered: