From 95c8a92b8859784c8ef2bdabe0706131a43bc755 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 31 Mar 2019 16:52:50 -0700 Subject: [PATCH] Add getIndexedAccessOrUndefined function --- src/compiler/checker.ts | 87 +++++++++++++++------------- src/compiler/diagnosticMessages.json | 4 ++ 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cc2dec0a705..4a770790384 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7195,8 +7195,7 @@ namespace ts { return type; } if (type.flags & TypeFlags.Index) { - const keys = getIndexType(getApparentType((type).type)); - return isIndexType ? filterType(keys, t => !!(t.flags & TypeFlags.Literal)) : keys; + return getIndexType(getApparentType((type).type)); } if (type.flags & TypeFlags.Conditional) { if ((type).root.isDistributive) { @@ -7491,8 +7490,8 @@ namespace ts { function getConstraintFromIndexedAccess(type: IndexedAccessType) { const objectType = getConstraintOfType(type.objectType) || type.objectType; if (objectType !== type.objectType) { - const constraint = getIndexedAccessType(objectType, type.indexType, /*accessNode*/ undefined, errorType); - if (constraint && constraint !== errorType) { + const constraint = getIndexedAccessTypeOrUndefined(objectType, type.indexType); + if (constraint) { return constraint; } } @@ -7686,8 +7685,8 @@ namespace ts { if (t.flags & TypeFlags.IndexedAccess) { const baseObjectType = getBaseConstraint((t).objectType); const baseIndexType = getBaseConstraint((t).indexType); - const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType, /*accessNode*/ undefined, errorType) : undefined; - return baseIndexedAccess && baseIndexedAccess !== errorType ? getBaseConstraint(baseIndexedAccess) : undefined; + const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType); + return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); } if (t.flags & TypeFlags.Conditional) { const constraint = getConstraintFromConditionalType(t); @@ -9875,7 +9874,7 @@ namespace ts { return false; } - function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, cacheSymbol: boolean, missingType: Type) { + function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, cacheSymbol: boolean) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; const propName = isTypeUsableAsPropertyName(indexType) ? getPropertyNameFromType(indexType) : @@ -9892,7 +9891,7 @@ namespace ts { markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword); if (isAssignmentTarget(accessExpression) && (isReferenceToReadonlyEntity(accessExpression, prop) || isReferenceThroughNamespaceImport(accessExpression))) { error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); - return missingType; + return undefined; } if (cacheSymbol) { getNodeLinks(accessNode!).resolvedSymbol = prop; @@ -9921,16 +9920,19 @@ namespace ts { if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) { return objectType; } - const indexInfo = accessExpression && isAssignmentTarget(accessExpression) && isGenericObjectType(originalObjectType) ? undefined : - isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || - getIndexInfoOfType(objectType, IndexKind.String) || - undefined; + const isAssignment = accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression)); + if (isAssignment && maybeTypeOfKind(originalObjectType, TypeFlags.Instantiable)) { + error(accessExpression, Diagnostics.Type_0_cannot_be_indexed_by_type_1, typeToString(originalObjectType), typeToString(indexType)); + return undefined; + } + const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || + getIndexInfoOfType(objectType, IndexKind.String); if (indexInfo) { if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { 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))) { + else if (isAssignment && indexInfo.isReadonly) { error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } return indexInfo.type; @@ -9964,7 +9966,7 @@ namespace ts { } } } - return missingType; + return undefined; } } if (isJSLiteralType(objectType)) { @@ -9985,7 +9987,7 @@ namespace ts { if (isTypeAny(indexType)) { return indexType; } - return missingType; + return undefined; } function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { @@ -10076,7 +10078,11 @@ namespace ts { return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); } - function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, missingType = accessNode ? errorType : unknownType): Type { + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, writing?: boolean): Type { + return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, writing) || (accessNode ? errorType : unknownType); + } + + function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, writing?: boolean): Type | undefined { if (objectType === wildcardType || indexType === wildcardType) { return wildcardType; } @@ -10105,25 +10111,25 @@ namespace ts { const propTypes: Type[] = []; let wasMissingProp = false; for (const t of (indexType).types) { - const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, accessNode, /*cacheSymbol*/ false, missingType); - if (propType === missingType) { - if (!accessNode) { - // If there's no error node, we can immeditely stop, since error reporting is off - return missingType; - } - else { - // Otherwise we set a flag and return at the end of the loop so we still mark all errors - wasMissingProp = true; - } + const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, accessNode, /*cacheSymbol*/ false); + if (propType) { + propTypes.push(propType); + } + else if (!accessNode) { + // If there's no error node, we can immeditely stop, since error reporting is off + return undefined; + } + else { + // Otherwise we set a flag and return at the end of the loop so we still mark all errors + wasMissingProp = true; } - propTypes.push(propType); } if (wasMissingProp) { - return missingType; + return undefined; } - return !accessNode || getAssignmentTargetKind(accessNode) === AssignmentKind.None ? getUnionType(propTypes) : getIntersectionType(propTypes); + return writing ? getIntersectionType(propTypes) : getUnionType(propTypes); } - return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, accessNode, /*cacheSymbol*/ true, missingType); + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, accessNode, /*cacheSymbol*/ true); } function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { @@ -11512,10 +11518,10 @@ namespace ts { let reportedError = false; for (let status = iterator.next(); !status.done; status = iterator.next()) { const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; - const targetPropType = getIndexedAccessType(target, nameType, /*accessNode*/ undefined, errorType); - if (targetPropType === errorType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables - const sourcePropType = getIndexedAccessType(source, nameType, /*accessNode*/ undefined, errorType); - if (sourcePropType !== errorType && targetPropType !== errorType && !checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { + const targetPropType = getIndexedAccessTypeOrUndefined(target, nameType); + if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables + const sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); + if (sourcePropType && !checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined); if (elaborated) { reportedError = true; @@ -12819,9 +12825,7 @@ namespace ts { if (indexType.flags & TypeFlags.StructuredOrInstantiable) { const keyType = getLowerBoundOfKeyType(indexType, /*isIndexType*/ true); if (keyType !== indexType && !(keyType.flags & TypeFlags.Never)) { - const targetType = keyType.flags & TypeFlags.Union ? - getIntersectionType(map((keyType).types, t => getIndexedAccessType(objectType, t))) : - getIndexedAccessType(objectType, keyType); + const targetType = getIndexedAccessType(objectType, keyType, /*accessNode*/ undefined, /*writing*/ true); if (result = isRelatedTo(source, targetType, reportErrors)) { return result; } @@ -12830,7 +12834,8 @@ namespace ts { else { const constraint = getConstraintOfType(objectType); if (constraint) { - if (result = isRelatedTo(source, getIndexedAccessType(constraint, indexType), reportErrors)) { + const targetType = getIndexedAccessType(constraint, indexType, /*accessNode*/ undefined, /*writing*/ true); + if (result = isRelatedTo(source, targetType, reportErrors)) { return result; } } @@ -19975,7 +19980,9 @@ namespace ts { return errorType; } - return checkIndexedAccessIndexType(getIndexedAccessType(objectType, isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType, node), node); + const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; + const indexedAccessType = getIndexedAccessType(objectType, effectiveIndexType, node, isAssignmentTarget(node)); + return checkIndexedAccessIndexType(indexedAccessType, node); } function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean { @@ -24570,7 +24577,7 @@ namespace ts { return type; } error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); - return type; + return errorType; } function checkIndexedAccessType(node: IndexedAccessTypeNode) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9ae3e1ca77e..3a2cdbe11eb 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2152,6 +2152,10 @@ "category": "Error", "code": 2593 }, + "Type '{0}' cannot be indexed by type '{1}'.": { + "category": "Error", + "code": 2594 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600