-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Check destructuring validity the same way element accesses and indexed accesses are checked #24700
Changes from 5 commits
26b3665
e2b492a
5b900c7
d933812
8250384
96ad54c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4635,6 +4635,10 @@ namespace ts { | |
if (isTypeAny(parentType)) { | ||
return parentType; | ||
} | ||
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation | ||
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { | ||
parentType = getNonNullableType(parentType); | ||
} | ||
|
||
let type: Type | undefined; | ||
if (pattern.kind === SyntaxKind.ObjectBindingPattern) { | ||
|
@@ -4654,53 +4658,13 @@ namespace ts { | |
else { | ||
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) | ||
const name = declaration.propertyName || <Identifier>declaration.name; | ||
const isLate = isLateBindableName(name); | ||
const isWellKnown = isComputedPropertyName(name) && isWellKnownSymbolSyntactically(name.expression); | ||
if (!isLate && !isWellKnown && isComputedNonLiteralName(name)) { | ||
const exprType = checkExpression((name as ComputedPropertyName).expression); | ||
if (isTypeAssignableToKind(exprType, TypeFlags.ESSymbolLike)) { | ||
if (noImplicitAny) { | ||
error(declaration, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(exprType), typeToString(parentType)); | ||
} | ||
return anyType; | ||
} | ||
const indexerType = isTypeAssignableToKind(exprType, TypeFlags.NumberLike) && getIndexTypeOfType(parentType, IndexKind.Number) || getIndexTypeOfType(parentType, IndexKind.String); | ||
if (!indexerType && noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors) { | ||
if (getIndexTypeOfType(parentType, IndexKind.Number)) { | ||
error(declaration, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); | ||
} | ||
else { | ||
error(declaration, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(parentType)); | ||
} | ||
} | ||
return indexerType || anyType; | ||
} | ||
|
||
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature, | ||
// or otherwise the type of the string index signature. | ||
const nameType = isLate ? checkComputedPropertyName(name as ComputedPropertyName) as LiteralType | UniqueESSymbolType : undefined; | ||
const text = isLate ? getLateBoundNameFromType(nameType!) : | ||
isWellKnown ? getPropertyNameForKnownSymbolName(idText(((name as ComputedPropertyName).expression as PropertyAccessExpression).name)) : | ||
getTextOfPropertyName(name); | ||
|
||
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation | ||
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { | ||
parentType = getNonNullableType(parentType); | ||
} | ||
if (isLate && nameType && !getPropertyOfType(parentType, text) && isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike)) { | ||
if (noImplicitAny) { | ||
error(declaration, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(nameType), typeToString(parentType)); | ||
} | ||
return anyType; | ||
} | ||
const declaredType = getConstraintForLocation(getTypeOfPropertyOfType(parentType, text), declaration.name); | ||
type = declaredType && getFlowTypeOfReference(declaration, declaredType) || | ||
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) || | ||
getIndexTypeOfType(parentType, IndexKind.String); | ||
if (!type) { | ||
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name)); | ||
return errorType; | ||
} | ||
const exprType = isComputedPropertyName(name) | ||
? checkExpression(name.expression) | ||
: isIdentifier(name) | ||
? getLiteralType(unescapeLeadingUnderscores(name.escapedText)) | ||
: checkExpression(name); | ||
const declaredType = checkIndexedAccessIndexType(getIndexedAccessType(getApparentType(parentType), exprType, name), name); | ||
type = getFlowTypeOfReference(declaration, getConstraintForLocation(declaredType, declaration.name)); | ||
} | ||
} | ||
else { | ||
|
@@ -9359,12 +9323,16 @@ namespace ts { | |
return false; | ||
} | ||
|
||
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | undefined, cacheSymbol: boolean, missingType: Type) { | ||
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | undefined, cacheSymbol: boolean, missingType: Type) { | ||
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; | ||
const propName = isTypeUsableAsLateBoundName(indexType) ? getLateBoundNameFromType(indexType) : | ||
accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ? | ||
getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name)) : | ||
undefined; | ||
const propName = isTypeUsableAsLateBoundName(indexType) | ||
? getLateBoundNameFromType(indexType) | ||
: accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) | ||
? getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name)) | ||
: accessNode && isPropertyName(accessNode) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need this case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needed to handle well-known symbol names, which are not normal property names or unique symbols: let { [Symbol.iterator]: destructured } = [] |
||
// late bound names are handled in the first branch, so here we only need to handle normal names | ||
? getPropertyNameForPropertyNameNode(accessNode) | ||
: undefined; | ||
if (propName !== undefined) { | ||
const prop = getPropertyOfType(objectType, propName); | ||
if (prop) { | ||
|
@@ -9385,7 +9353,7 @@ namespace ts { | |
} | ||
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) { | ||
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement)) { | ||
const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.indexType; | ||
const indexNode = getIndexNodeForAccessExpression(accessNode); | ||
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); | ||
} | ||
return mapType(objectType, t => getRestTypeOfTupleType(<TupleTypeReference>t) || undefinedType); | ||
|
@@ -9400,7 +9368,7 @@ namespace ts { | |
undefined; | ||
if (indexInfo) { | ||
if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { | ||
const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.indexType; | ||
const indexNode = getIndexNodeForAccessExpression(accessNode); | ||
error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); | ||
} | ||
else if (accessExpression && indexInfo.isReadonly && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { | ||
|
@@ -9441,7 +9409,7 @@ namespace ts { | |
return anyType; | ||
} | ||
if (accessNode) { | ||
const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.indexType; | ||
const indexNode = getIndexNodeForAccessExpression(accessNode); | ||
if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { | ||
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (<LiteralType>indexType).value, typeToString(objectType)); | ||
} | ||
|
@@ -9458,6 +9426,16 @@ namespace ts { | |
return missingType; | ||
} | ||
|
||
function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName) { | ||
return accessNode.kind === SyntaxKind.ElementAccessExpression | ||
? accessNode.argumentExpression | ||
: accessNode.kind === SyntaxKind.IndexedAccessType | ||
? accessNode.indexType | ||
: accessNode.kind === SyntaxKind.ComputedPropertyName | ||
? accessNode.expression | ||
: accessNode; | ||
} | ||
|
||
function isGenericObjectType(type: Type): boolean { | ||
return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.GenericMappedType); | ||
} | ||
|
@@ -9521,7 +9499,7 @@ namespace ts { | |
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); | ||
} | ||
|
||
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode, missingType = accessNode ? errorType : unknownType): Type { | ||
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName, missingType = accessNode ? errorType : unknownType): Type { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No extra flag please. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's now gone, and I'm using the raw apparent type as the input directly instead. The downside is that error messages now reference the apparent type instead of the original type, too. |
||
if (objectType === wildcardType || indexType === wildcardType) { | ||
return wildcardType; | ||
} | ||
|
@@ -23205,7 +23183,7 @@ namespace ts { | |
forEach(node.types, checkSourceElement); | ||
} | ||
|
||
function checkIndexedAccessIndexType(type: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode) { | ||
function checkIndexedAccessIndexType(type: Type, accessNode: Node) { | ||
if (!(type.flags & TypeFlags.IndexedAccess)) { | ||
return type; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts(1,11): error TS2459: Type 'number' has no property 'x' and no string index signature. | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts(1,21): error TS2459: Type 'number' has no property 'y' and no string index signature. | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts(1,11): error TS2339: Property 'x' does not exist on type 'Number'. | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts(1,21): error TS2339: Property 'y' does not exist on type 'Number'. | ||
|
||
|
||
==== tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts (2 errors) ==== | ||
for (var {x: a = 0, y: b = 1} of [2, 3]) { | ||
~ | ||
!!! error TS2459: Type 'number' has no property 'x' and no string index signature. | ||
!!! error TS2339: Property 'x' does not exist on type 'Number'. | ||
~ | ||
!!! error TS2459: Type 'number' has no property 'y' and no string index signature. | ||
!!! error TS2339: Property 'y' does not exist on type 'Number'. | ||
a; | ||
b; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts(1,13): error TS2459: Type 'number' has no property 'x' and no string index signature. | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts(1,23): error TS2459: Type 'number' has no property 'y' and no string index signature. | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts(1,13): error TS2339: Property 'x' does not exist on type 'Number'. | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts(1,23): error TS2339: Property 'y' does not exist on type 'Number'. | ||
|
||
|
||
==== tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts (2 errors) ==== | ||
for (const {x: a = 0, y: b = 1} of [2, 3]) { | ||
~ | ||
!!! error TS2459: Type 'number' has no property 'x' and no string index signature. | ||
!!! error TS2339: Property 'x' does not exist on type 'Number'. | ||
~ | ||
!!! error TS2459: Type 'number' has no property 'y' and no string index signature. | ||
!!! error TS2339: Property 'y' does not exist on type 'Number'. | ||
a; | ||
b; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts(1,13): error TS2459: Type 'number' has no property 'x' and no string index signature. | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts(1,23): error TS2459: Type 'number' has no property 'y' and no string index signature. | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts(1,13): error TS2339: Property 'x' does not exist on type 'Number'. | ||
tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts(1,23): error TS2339: Property 'y' does not exist on type 'Number'. | ||
|
||
|
||
==== tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts (2 errors) ==== | ||
for (const {x: a = 0, y: b = 1} of [2, 3]) { | ||
~ | ||
!!! error TS2459: Type 'number' has no property 'x' and no string index signature. | ||
!!! error TS2339: Property 'x' does not exist on type 'Number'. | ||
~ | ||
!!! error TS2459: Type 'number' has no property 'y' and no string index signature. | ||
!!! error TS2339: Property 'y' does not exist on type 'Number'. | ||
a; | ||
b; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,32 @@ | ||
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(2,12): error TS2448: Block-scoped variable 'a' used before its declaration. | ||
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(2,12): error TS2538: Type 'any' cannot be used as an index type. | ||
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(5,12): error TS2448: Block-scoped variable 'a' used before its declaration. | ||
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(5,12): error TS2538: Type 'any' cannot be used as an index type. | ||
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(8,7): error TS2448: Block-scoped variable 'b' used before its declaration. | ||
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(8,7): error TS2538: Type 'any' cannot be used as an index type. | ||
|
||
|
||
==== tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts (3 errors) ==== | ||
==== tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts (6 errors) ==== | ||
// 1: | ||
for (let {[a]: a} of [{ }]) continue; | ||
~ | ||
!!! error TS2448: Block-scoped variable 'a' used before its declaration. | ||
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:2:16: 'a' is declared here. | ||
~ | ||
!!! error TS2538: Type 'any' cannot be used as an index type. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't feel like a legal error, but it seems like we're only issuing it when there's already an error on the same node. Is there an issue with the error type leaking out into an error message? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most certainly. It's because we choose to consider indexing with |
||
|
||
// 2: | ||
for (let {[a]: a} = { }; false; ) continue; | ||
~ | ||
!!! error TS2448: Block-scoped variable 'a' used before its declaration. | ||
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:5:16: 'a' is declared here. | ||
~ | ||
!!! error TS2538: Type 'any' cannot be used as an index type. | ||
|
||
// 3: | ||
let {[b]: b} = { }; | ||
~ | ||
!!! error TS2448: Block-scoped variable 'b' used before its declaration. | ||
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:8:11: 'b' is declared here. | ||
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:8:11: 'b' is declared here. | ||
~ | ||
!!! error TS2538: Type 'any' cannot be used as an index type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
checkComputedPropertyName
here.