Skip to content

Commit 671f7a8

Browse files
committed
Defer indexed access T[K] where T is generic and K is non-generic
1 parent b7e8a6d commit 671f7a8

File tree

2 files changed

+70
-28
lines changed

2 files changed

+70
-28
lines changed

src/compiler/checker.ts

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4667,33 +4667,24 @@ namespace ts {
46674667
* The apparent type of a type parameter is the base constraint instantiated with the type parameter
46684668
* as the type argument for the 'this' type.
46694669
*/
4670-
function getApparentTypeOfTypeParameter(type: TypeParameter) {
4670+
function getApparentTypeOfTypeVariable(type: TypeVariable) {
46714671
if (!type.resolvedApparentType) {
4672-
let constraintType = getConstraintOfTypeParameter(type);
4672+
let constraintType = getConstraintOfTypeVariable(type);
46734673
while (constraintType && constraintType.flags & TypeFlags.TypeParameter) {
4674-
constraintType = getConstraintOfTypeParameter(<TypeParameter>constraintType);
4674+
constraintType = getConstraintOfTypeVariable(<TypeVariable>constraintType);
46754675
}
46764676
type.resolvedApparentType = getTypeWithThisArgument(constraintType || emptyObjectType, type);
46774677
}
46784678
return type.resolvedApparentType;
46794679
}
46804680

4681-
/**
4682-
* The apparent type of an indexed access T[K] is the type of T's string index signature, if any.
4683-
*/
4684-
function getApparentTypeOfIndexedAccess(type: IndexedAccessType) {
4685-
return getIndexTypeOfType(getApparentType(type.objectType), IndexKind.String) || type;
4686-
}
4687-
46884681
/**
46894682
* For a type parameter, return the base constraint of the type parameter. For the string, number,
46904683
* boolean, and symbol primitive types, return the corresponding object types. Otherwise return the
46914684
* type itself. Note that the apparent type of a union type is the union type itself.
46924685
*/
46934686
function getApparentType(type: Type): Type {
4694-
const t = type.flags & TypeFlags.TypeParameter ? getApparentTypeOfTypeParameter(<TypeParameter>type) :
4695-
type.flags & TypeFlags.IndexedAccess ? getApparentTypeOfIndexedAccess(<IndexedAccessType>type) :
4696-
type;
4687+
const t = type.flags & TypeFlags.TypeVariable ? getApparentTypeOfTypeVariable(<TypeVariable>type) : type;
46974688
return t.flags & TypeFlags.StringLike ? globalStringType :
46984689
t.flags & TypeFlags.NumberLike ? globalNumberType :
46994690
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
@@ -5279,6 +5270,31 @@ namespace ts {
52795270
return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint;
52805271
}
52815272

5273+
function getConstraintOfIndexedAccess(type: IndexedAccessType): Type {
5274+
// The constraint of T[K], where T is an object, union, or intersection type,
5275+
// is the type of the string index signature of T, if any.
5276+
if (type.objectType.flags & TypeFlags.StructuredType) {
5277+
return getIndexTypeOfType(type.objectType, IndexKind.String);
5278+
}
5279+
// The constraint of T[K], where T is a type variable, is A[K], where A is the
5280+
// apparent type of T.
5281+
if (type.objectType.flags & TypeFlags.TypeVariable) {
5282+
const apparentType = getApparentTypeOfTypeVariable(<TypeVariable>type.objectType);
5283+
if (apparentType !== emptyObjectType) {
5284+
return isTypeOfKind((<IndexedAccessType>type).indexType, TypeFlags.StringLike) ?
5285+
getIndexedAccessType(apparentType, (<IndexedAccessType>type).indexType) :
5286+
getIndexTypeOfType(apparentType, IndexKind.String);
5287+
}
5288+
}
5289+
return undefined;
5290+
}
5291+
5292+
function getConstraintOfTypeVariable(type: TypeVariable): Type {
5293+
return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>type) :
5294+
type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(<IndexedAccessType>type) :
5295+
undefined;
5296+
}
5297+
52825298
function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol {
52835299
return getSymbolOfNode(getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter).parent);
52845300
}
@@ -6032,11 +6048,16 @@ namespace ts {
60326048
}
60336049

60346050
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) {
6035-
if (maybeTypeOfKind(indexType, TypeFlags.TypeVariable | TypeFlags.Index) || isGenericMappedType(objectType)) {
6036-
// If the index type is generic or if the object type is a mapped type with a generic constraint,
6037-
// we are performing a higher-order index access where we cannot meaningfully access the properties
6038-
// of the object type. In those cases, we first check that the index type is assignable to 'keyof T'
6039-
// for the object type.
6051+
// If the index type is generic, if the object type is generic and doesn't originate in an expression,
6052+
// or if the object type is a mapped type with a generic constraint, we are performing a higher-order
6053+
// index access where we cannot meaningfully access the properties of the object type. Note that for a
6054+
// generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to
6055+
// preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved
6056+
// eagerly using the constraint type of 'this' at the given location.
6057+
if (maybeTypeOfKind(indexType, TypeFlags.TypeVariable | TypeFlags.Index) ||
6058+
maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) ||
6059+
isGenericMappedType(objectType)) {
6060+
// We first check that the index type is assignable to 'keyof T' for the object type.
60406061
if (accessNode) {
60416062
if (!isTypeAssignableTo(indexType, getIndexType(objectType))) {
60426063
error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType));
@@ -6053,6 +6074,7 @@ namespace ts {
60536074
const id = objectType.id + "," + indexType.id;
60546075
return indexedAccessTypes[id] || (indexedAccessTypes[id] = createIndexedAccessType(objectType, indexType));
60556076
}
6077+
// In the following we resolve T[K] to the type of the property in T selected by K.
60566078
const apparentObjectType = getApparentType(objectType);
60576079
if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Primitive)) {
60586080
const propTypes: Type[] = [];
@@ -7240,8 +7262,7 @@ namespace ts {
72407262
return result;
72417263
}
72427264
}
7243-
7244-
if (target.flags & TypeFlags.TypeParameter) {
7265+
else if (target.flags & TypeFlags.TypeParameter) {
72457266
// A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P].
72467267
if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>source) === getIndexType(target)) {
72477268
if (!(<MappedType>source).declaration.questionToken) {
@@ -7270,10 +7291,10 @@ namespace ts {
72707291
return result;
72717292
}
72727293
}
7273-
// Given a type parameter T with a constraint C, a type S is assignable to
7294+
// Given a type variable T with a constraint C, a type S is assignable to
72747295
// keyof T if S is assignable to keyof C.
7275-
if ((<IndexType>target).type.flags & TypeFlags.TypeParameter) {
7276-
const constraint = getConstraintOfTypeParameter(<TypeParameter>(<IndexType>target).type);
7296+
if ((<IndexType>target).type.flags & TypeFlags.TypeVariable) {
7297+
const constraint = getConstraintOfTypeVariable(<TypeVariable>(<IndexType>target).type);
72777298
if (constraint) {
72787299
if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) {
72797300
return result;
@@ -7289,6 +7310,15 @@ namespace ts {
72897310
return result;
72907311
}
72917312
}
7313+
// A type S is related to a type T[K] if S is related to A[K], where K is string-like and
7314+
// A is the apparent type of S.
7315+
const constraint = getConstraintOfIndexedAccess(<IndexedAccessType>target);
7316+
if (constraint) {
7317+
if (result = isRelatedTo(source, constraint, reportErrors)) {
7318+
errorInfo = saveErrorInfo;
7319+
return result;
7320+
}
7321+
}
72927322
}
72937323

72947324
if (source.flags & TypeFlags.TypeParameter) {
@@ -7297,6 +7327,7 @@ namespace ts {
72977327
const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(<MappedType>target));
72987328
const templateType = getTemplateTypeFromMappedType(<MappedType>target);
72997329
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
7330+
errorInfo = saveErrorInfo;
73007331
return result;
73017332
}
73027333
}
@@ -7318,6 +7349,17 @@ namespace ts {
73187349
}
73197350
}
73207351
}
7352+
else if (source.flags & TypeFlags.IndexedAccess) {
7353+
// A type S[K] is related to a type T if A[K] is related to T, where K is string-like and
7354+
// A is the apparent type of S.
7355+
const constraint = getConstraintOfIndexedAccess(<IndexedAccessType>source);
7356+
if (constraint) {
7357+
if (result = isRelatedTo(constraint, target, reportErrors)) {
7358+
errorInfo = saveErrorInfo;
7359+
return result;
7360+
}
7361+
}
7362+
}
73217363
else {
73227364
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
73237365
// We have type references to same target type, see if relationship holds for all type arguments
@@ -14978,8 +15020,8 @@ namespace ts {
1497815020

1497915021
function isLiteralContextualType(contextualType: Type) {
1498015022
if (contextualType) {
14981-
if (contextualType.flags & TypeFlags.TypeParameter) {
14982-
const apparentType = getApparentTypeOfTypeParameter(<TypeParameter>contextualType);
15023+
if (contextualType.flags & TypeFlags.TypeVariable) {
15024+
const apparentType = getApparentTypeOfTypeVariable(<TypeVariable>contextualType);
1498315025
// If the type parameter is constrained to the base primitive type we're checking for,
1498415026
// consider this a literal context. For example, given a type parameter 'T extends string',
1498515027
// this causes us to infer string literal types for T.
@@ -15814,7 +15856,7 @@ namespace ts {
1581415856
checkSourceElement(node.type);
1581515857
const type = <MappedType>getTypeFromMappedTypeNode(node);
1581615858
const constraintType = getConstraintTypeFromMappedType(type);
15817-
const keyType = constraintType.flags & TypeFlags.TypeParameter ? getApparentTypeOfTypeParameter(<TypeParameter>constraintType) : constraintType;
15859+
const keyType = constraintType.flags & TypeFlags.TypeVariable ? getApparentTypeOfTypeVariable(<TypeVariable>constraintType) : constraintType;
1581815860
checkTypeAssignableTo(keyType, stringType, node.typeParameter.constraint);
1581915861
}
1582015862

src/compiler/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2967,6 +2967,8 @@ namespace ts {
29672967
}
29682968

29692969
export interface TypeVariable extends Type {
2970+
/* @internal */
2971+
resolvedApparentType: Type;
29702972
/* @internal */
29712973
resolvedIndexType: IndexType;
29722974
}
@@ -2979,8 +2981,6 @@ namespace ts {
29792981
/* @internal */
29802982
mapper?: TypeMapper; // Instantiation mapper
29812983
/* @internal */
2982-
resolvedApparentType: Type;
2983-
/* @internal */
29842984
isThisType?: boolean;
29852985
}
29862986

0 commit comments

Comments
 (0)