Skip to content

Commit d69aeb4

Browse files
committed
T[K] ignores index signatures in T's constraint
1 parent e79e0bd commit d69aeb4

File tree

1 file changed

+37
-24
lines changed

1 file changed

+37
-24
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,13 @@ namespace ts {
706706
Signature = 1 << 0, // Obtaining contextual signature
707707
}
708708

709+
const enum AccessFlags {
710+
None = 0,
711+
NoIndexSignatures = 1 << 0,
712+
Writing = 1 << 1,
713+
CacheSymbol = 1 << 2,
714+
}
715+
709716
const enum CallbackCheck {
710717
None,
711718
Bivariant,
@@ -9796,16 +9803,16 @@ namespace ts {
97969803
return numberIndexInfo !== enumNumberIndexInfo ? numberIndexInfo : undefined;
97979804
}
97989805

9799-
function getIndexType(type: Type, stringsOnly = keyofStringsOnly): Type {
9800-
return type.flags & TypeFlags.Union ? getIntersectionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly))) :
9801-
type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly))) :
9806+
function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type {
9807+
return type.flags & TypeFlags.Union ? getIntersectionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
9808+
type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
98029809
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
98039810
getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
98049811
type === wildcardType ? wildcardType :
98059812
type.flags & TypeFlags.Any ? keyofConstraintType :
9806-
stringsOnly ? getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
9807-
getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
9808-
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
9813+
stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
9814+
!noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
9815+
!noIndexSignatures && getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
98099816
getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique);
98109817
}
98119818

@@ -9877,7 +9884,7 @@ namespace ts {
98779884
return false;
98789885
}
98799886

9880-
function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, writing: boolean, cacheSymbol: boolean) {
9887+
function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
98819888
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
98829889
const propName = isTypeUsableAsPropertyName(indexType) ?
98839890
getPropertyNameFromType(indexType) :
@@ -9896,7 +9903,7 @@ namespace ts {
98969903
error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop));
98979904
return undefined;
98989905
}
9899-
if (cacheSymbol) {
9906+
if (accessFlags & AccessFlags.CacheSymbol) {
99009907
getNodeLinks(accessNode!).resolvedSymbol = prop;
99019908
}
99029909
}
@@ -9926,19 +9933,22 @@ namespace ts {
99269933
const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) ||
99279934
getIndexInfoOfType(objectType, IndexKind.String);
99289935
if (indexInfo) {
9929-
const isAssignment = accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression));
9930-
if (isAssignment && maybeTypeOfKind(originalObjectType, TypeFlags.Instantiable)) {
9931-
error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType));
9936+
if (accessFlags & AccessFlags.NoIndexSignatures) {
9937+
if (accessExpression) {
9938+
error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType));
9939+
}
99329940
return undefined;
99339941
}
99349942
if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) {
99359943
const indexNode = getIndexNodeForAccessExpression(accessNode);
99369944
error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType));
9945+
return indexInfo.type;
99379946
}
9938-
else if (isAssignment && indexInfo.isReadonly) {
9939-
error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));
9940-
}
9941-
else if (writing && indexInfo.isReadonly) {
9947+
if (indexInfo.isReadonly && (accessFlags & AccessFlags.Writing || accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression)))) {
9948+
if (accessExpression) {
9949+
error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));
9950+
return indexInfo.type;
9951+
}
99429952
return undefined;
99439953
}
99449954
return indexInfo.type;
@@ -10085,10 +10095,10 @@ namespace ts {
1008510095
}
1008610096

1008710097
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression): Type {
10088-
return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, /*writing*/ false) || (accessNode ? errorType : unknownType);
10098+
return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, AccessFlags.None) || (accessNode ? errorType : unknownType);
1008910099
}
1009010100

10091-
function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, writing = false): Type | undefined {
10101+
function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None): Type | undefined {
1009210102
if (objectType === wildcardType || indexType === wildcardType) {
1009310103
return wildcardType;
1009410104
}
@@ -10117,7 +10127,7 @@ namespace ts {
1011710127
const propTypes: Type[] = [];
1011810128
let wasMissingProp = false;
1011910129
for (const t of (<UnionType>indexType).types) {
10120-
const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, accessNode, writing, /*cacheSymbol*/ false);
10130+
const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, accessNode, accessFlags);
1012110131
if (propType) {
1012210132
propTypes.push(propType);
1012310133
}
@@ -10133,9 +10143,9 @@ namespace ts {
1013310143
if (wasMissingProp) {
1013410144
return undefined;
1013510145
}
10136-
return writing ? getIntersectionType(propTypes) : getUnionType(propTypes);
10146+
return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes) : getUnionType(propTypes);
1013710147
}
10138-
return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, accessNode, writing, /*cacheSymbol*/ true);
10148+
return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol);
1013910149
}
1014010150

1014110151
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
@@ -12831,7 +12841,7 @@ namespace ts {
1283112841
if (indexType.flags & TypeFlags.StructuredOrInstantiable) {
1283212842
const keyType = getLowerBoundOfKeyType(indexType, /*isIndexType*/ true);
1283312843
if (keyType !== indexType && !(keyType.flags & TypeFlags.Never)) {
12834-
const targetType = getIndexedAccessTypeOrUndefined(objectType, keyType, /*accessNode*/ undefined, /*writing*/ true);
12844+
const targetType = getIndexedAccessTypeOrUndefined(objectType, keyType, /*accessNode*/ undefined, AccessFlags.Writing);
1283512845
if (targetType && (result = isRelatedTo(source, targetType, reportErrors))) {
1283612846
return result;
1283712847
}
@@ -12840,7 +12850,7 @@ namespace ts {
1284012850
else {
1284112851
const constraint = getConstraintOfType(objectType);
1284212852
if (constraint) {
12843-
const targetType = getIndexedAccessTypeOrUndefined(constraint, indexType, /*accessNode*/ undefined, /*writing*/ true);
12853+
const targetType = getIndexedAccessTypeOrUndefined(constraint, indexType, /*accessNode*/ undefined, AccessFlags.Writing | AccessFlags.NoIndexSignatures);
1284412854
if (targetType && (result = isRelatedTo(source, targetType, reportErrors))) {
1284512855
return result;
1284612856
}
@@ -12859,7 +12869,7 @@ namespace ts {
1285912869
}
1286012870
if (!isGenericMappedType(source)) {
1286112871
const targetConstraint = getConstraintTypeFromMappedType(target);
12862-
const sourceKeys = getIndexType(source);
12872+
const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true);
1286312873
const hasOptionalUnionKeys = modifiers & MappedTypeModifiers.IncludeOptional && targetConstraint.flags & TypeFlags.Union;
1286412874
const filteredByApplicability = hasOptionalUnionKeys ? filterType(targetConstraint, t => !!isRelatedTo(t, sourceKeys)) : undefined;
1286512875
// A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X.
@@ -19989,7 +19999,10 @@ namespace ts {
1998919999
}
1999020000

1999120001
const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType;
19992-
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, isAssignmentTarget(node)) || errorType;
20002+
const accessFlags = isAssignmentTarget(node) ?
20003+
AccessFlags.Writing | (isGenericObjectType(objectType) ? AccessFlags.NoIndexSignatures : 0) :
20004+
AccessFlags.None;
20005+
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType;
1999320006
return checkIndexedAccessIndexType(indexedAccessType, node);
1999420007
}
1999520008

0 commit comments

Comments
 (0)