Skip to content

Commit deeee77

Browse files
authored
Check destructuring validity the same way element accesses and indexed accesses are checked (#24700)
* Check destructuring validity the same way element accesses and indexed accesses are checked * Accept updated test baseline * Use raw apparent type instead of passing in flag to sometimes make one * Use `checkComputedPropertyName`
1 parent 4f6f713 commit deeee77

28 files changed

+335
-138
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4635,6 +4635,10 @@ namespace ts {
46354635
if (isTypeAny(parentType)) {
46364636
return parentType;
46374637
}
4638+
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
4639+
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) {
4640+
parentType = getNonNullableType(parentType);
4641+
}
46384642

46394643
let type: Type | undefined;
46404644
if (pattern.kind === SyntaxKind.ObjectBindingPattern) {
@@ -4654,53 +4658,13 @@ namespace ts {
46544658
else {
46554659
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
46564660
const name = declaration.propertyName || <Identifier>declaration.name;
4657-
const isLate = isLateBindableName(name);
4658-
const isWellKnown = isComputedPropertyName(name) && isWellKnownSymbolSyntactically(name.expression);
4659-
if (!isLate && !isWellKnown && isComputedNonLiteralName(name)) {
4660-
const exprType = checkExpression((name as ComputedPropertyName).expression);
4661-
if (isTypeAssignableToKind(exprType, TypeFlags.ESSymbolLike)) {
4662-
if (noImplicitAny) {
4663-
error(declaration, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(exprType), typeToString(parentType));
4664-
}
4665-
return anyType;
4666-
}
4667-
const indexerType = isTypeAssignableToKind(exprType, TypeFlags.NumberLike) && getIndexTypeOfType(parentType, IndexKind.Number) || getIndexTypeOfType(parentType, IndexKind.String);
4668-
if (!indexerType && noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors) {
4669-
if (getIndexTypeOfType(parentType, IndexKind.Number)) {
4670-
error(declaration, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number);
4671-
}
4672-
else {
4673-
error(declaration, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(parentType));
4674-
}
4675-
}
4676-
return indexerType || anyType;
4677-
}
4678-
4679-
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
4680-
// or otherwise the type of the string index signature.
4681-
const nameType = isLate ? checkComputedPropertyName(name as ComputedPropertyName) as LiteralType | UniqueESSymbolType : undefined;
4682-
const text = isLate ? getLateBoundNameFromType(nameType!) :
4683-
isWellKnown ? getPropertyNameForKnownSymbolName(idText(((name as ComputedPropertyName).expression as PropertyAccessExpression).name)) :
4684-
getTextOfPropertyName(name);
4685-
4686-
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
4687-
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) {
4688-
parentType = getNonNullableType(parentType);
4689-
}
4690-
if (isLate && nameType && !getPropertyOfType(parentType, text) && isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike)) {
4691-
if (noImplicitAny) {
4692-
error(declaration, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(nameType), typeToString(parentType));
4693-
}
4694-
return anyType;
4695-
}
4696-
const declaredType = getConstraintForLocation(getTypeOfPropertyOfType(parentType, text), declaration.name);
4697-
type = declaredType && getFlowTypeOfReference(declaration, declaredType) ||
4698-
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
4699-
getIndexTypeOfType(parentType, IndexKind.String);
4700-
if (!type) {
4701-
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name));
4702-
return errorType;
4703-
}
4661+
const exprType = isComputedPropertyName(name)
4662+
? checkComputedPropertyName(name)
4663+
: isIdentifier(name)
4664+
? getLiteralType(unescapeLeadingUnderscores(name.escapedText))
4665+
: checkExpression(name);
4666+
const declaredType = checkIndexedAccessIndexType(getIndexedAccessType(getApparentType(parentType), exprType, name), name);
4667+
type = getFlowTypeOfReference(declaration, getConstraintForLocation(declaredType, declaration.name));
47044668
}
47054669
}
47064670
else {
@@ -9359,12 +9323,16 @@ namespace ts {
93599323
return false;
93609324
}
93619325

9362-
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | undefined, cacheSymbol: boolean, missingType: Type) {
9326+
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | undefined, cacheSymbol: boolean, missingType: Type) {
93639327
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
9364-
const propName = isTypeUsableAsLateBoundName(indexType) ? getLateBoundNameFromType(indexType) :
9365-
accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ?
9366-
getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name)) :
9367-
undefined;
9328+
const propName = isTypeUsableAsLateBoundName(indexType)
9329+
? getLateBoundNameFromType(indexType)
9330+
: accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false)
9331+
? getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name))
9332+
: accessNode && isPropertyName(accessNode)
9333+
// late bound names are handled in the first branch, so here we only need to handle normal names
9334+
? getPropertyNameForPropertyNameNode(accessNode)
9335+
: undefined;
93689336
if (propName !== undefined) {
93699337
const prop = getPropertyOfType(objectType, propName);
93709338
if (prop) {
@@ -9385,7 +9353,7 @@ namespace ts {
93859353
}
93869354
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) {
93879355
if (accessNode && everyType(objectType, t => !(<TupleTypeReference>t).target.hasRestElement)) {
9388-
const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.indexType;
9356+
const indexNode = getIndexNodeForAccessExpression(accessNode);
93899357
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
93909358
}
93919359
return mapType(objectType, t => getRestTypeOfTupleType(<TupleTypeReference>t) || undefinedType);
@@ -9400,7 +9368,7 @@ namespace ts {
94009368
undefined;
94019369
if (indexInfo) {
94029370
if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) {
9403-
const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.indexType;
9371+
const indexNode = getIndexNodeForAccessExpression(accessNode);
94049372
error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType));
94059373
}
94069374
else if (accessExpression && indexInfo.isReadonly && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) {
@@ -9441,7 +9409,7 @@ namespace ts {
94419409
return anyType;
94429410
}
94439411
if (accessNode) {
9444-
const indexNode = accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.indexType;
9412+
const indexNode = getIndexNodeForAccessExpression(accessNode);
94459413
if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
94469414
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (<LiteralType>indexType).value, typeToString(objectType));
94479415
}
@@ -9458,6 +9426,16 @@ namespace ts {
94589426
return missingType;
94599427
}
94609428

9429+
function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName) {
9430+
return accessNode.kind === SyntaxKind.ElementAccessExpression
9431+
? accessNode.argumentExpression
9432+
: accessNode.kind === SyntaxKind.IndexedAccessType
9433+
? accessNode.indexType
9434+
: accessNode.kind === SyntaxKind.ComputedPropertyName
9435+
? accessNode.expression
9436+
: accessNode;
9437+
}
9438+
94619439
function isGenericObjectType(type: Type): boolean {
94629440
return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.GenericMappedType);
94639441
}
@@ -9521,7 +9499,7 @@ namespace ts {
95219499
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
95229500
}
95239501

9524-
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode, missingType = accessNode ? errorType : unknownType): Type {
9502+
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName, missingType = accessNode ? errorType : unknownType): Type {
95259503
if (objectType === wildcardType || indexType === wildcardType) {
95269504
return wildcardType;
95279505
}
@@ -23207,7 +23185,7 @@ namespace ts {
2320723185
forEach(node.types, checkSourceElement);
2320823186
}
2320923187

23210-
function checkIndexedAccessIndexType(type: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode) {
23188+
function checkIndexedAccessIndexType(type: Type, accessNode: Node) {
2321123189
if (!(type.flags & TypeFlags.IndexedAccess)) {
2321223190
return type;
2321323191
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts(1,11): error TS2459: Type 'number' has no property 'x' and no string index signature.
2-
tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts(1,21): error TS2459: Type 'number' has no property 'y' and no string index signature.
1+
tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts(1,11): error TS2339: Property 'x' does not exist on type 'Number'.
2+
tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts(1,21): error TS2339: Property 'y' does not exist on type 'Number'.
33

44

55
==== tests/cases/conformance/statements/for-ofStatements/ES5For-of27.ts (2 errors) ====
66
for (var {x: a = 0, y: b = 1} of [2, 3]) {
77
~
8-
!!! error TS2459: Type 'number' has no property 'x' and no string index signature.
8+
!!! error TS2339: Property 'x' does not exist on type 'Number'.
99
~
10-
!!! error TS2459: Type 'number' has no property 'y' and no string index signature.
10+
!!! error TS2339: Property 'y' does not exist on type 'Number'.
1111
a;
1212
b;
1313
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts(1,13): error TS2459: Type 'number' has no property 'x' and no string index signature.
2-
tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts(1,23): error TS2459: Type 'number' has no property 'y' and no string index signature.
1+
tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts(1,13): error TS2339: Property 'x' does not exist on type 'Number'.
2+
tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts(1,23): error TS2339: Property 'y' does not exist on type 'Number'.
33

44

55
==== tests/cases/conformance/statements/for-ofStatements/ES5For-of29.ts (2 errors) ====
66
for (const {x: a = 0, y: b = 1} of [2, 3]) {
77
~
8-
!!! error TS2459: Type 'number' has no property 'x' and no string index signature.
8+
!!! error TS2339: Property 'x' does not exist on type 'Number'.
99
~
10-
!!! error TS2459: Type 'number' has no property 'y' and no string index signature.
10+
!!! error TS2339: Property 'y' does not exist on type 'Number'.
1111
a;
1212
b;
1313
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts(1,13): error TS2459: Type 'number' has no property 'x' and no string index signature.
2-
tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts(1,23): error TS2459: Type 'number' has no property 'y' and no string index signature.
1+
tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts(1,13): error TS2339: Property 'x' does not exist on type 'Number'.
2+
tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts(1,23): error TS2339: Property 'y' does not exist on type 'Number'.
33

44

55
==== tests/cases/conformance/statements/for-ofStatements/ES5For-of35.ts (2 errors) ====
66
for (const {x: a = 0, y: b = 1} of [2, 3]) {
77
~
8-
!!! error TS2459: Type 'number' has no property 'x' and no string index signature.
8+
!!! error TS2339: Property 'x' does not exist on type 'Number'.
99
~
10-
!!! error TS2459: Type 'number' has no property 'y' and no string index signature.
10+
!!! error TS2339: Property 'y' does not exist on type 'Number'.
1111
a;
1212
b;
1313
}
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(2,12): error TS2448: Block-scoped variable 'a' used before its declaration.
2+
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(2,12): error TS2538: Type 'any' cannot be used as an index type.
23
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(5,12): error TS2448: Block-scoped variable 'a' used before its declaration.
4+
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(5,12): error TS2538: Type 'any' cannot be used as an index type.
35
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(8,7): error TS2448: Block-scoped variable 'b' used before its declaration.
6+
tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts(8,7): error TS2538: Type 'any' cannot be used as an index type.
47

58

6-
==== tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts (3 errors) ====
9+
==== tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts (6 errors) ====
710
// 1:
811
for (let {[a]: a} of [{ }]) continue;
912
~
1013
!!! error TS2448: Block-scoped variable 'a' used before its declaration.
1114
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:2:16: 'a' is declared here.
15+
~
16+
!!! error TS2538: Type 'any' cannot be used as an index type.
1217

1318
// 2:
1419
for (let {[a]: a} = { }; false; ) continue;
1520
~
1621
!!! error TS2448: Block-scoped variable 'a' used before its declaration.
1722
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:5:16: 'a' is declared here.
23+
~
24+
!!! error TS2538: Type 'any' cannot be used as an index type.
1825

1926
// 3:
2027
let {[b]: b} = { };
2128
~
2229
!!! error TS2448: Block-scoped variable 'b' used before its declaration.
23-
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:8:11: 'b' is declared here.
30+
!!! related TS2728 tests/cases/compiler/blockScopedBindingUsedBeforeDef.ts:8:11: 'b' is declared here.
31+
~
32+
!!! error TS2538: Type 'any' cannot be used as an index type.

0 commit comments

Comments
 (0)