Skip to content

Commit 57cb4ac

Browse files
authored
Merge pull request #12770 from Microsoft/deferIndexedAccess
Defer indexed access T[K] with non-generic K
2 parents f27fe0d + 29f6e7f commit 57cb4ac

10 files changed

+1222
-290
lines changed

src/compiler/checker.ts

Lines changed: 89 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,20 +3472,7 @@ namespace ts {
34723472
}
34733473

34743474
if (!popTypeResolution()) {
3475-
if ((<VariableLikeDeclaration>symbol.valueDeclaration).type) {
3476-
// Variable has type annotation that circularly references the variable itself
3477-
type = unknownType;
3478-
error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation,
3479-
symbolToString(symbol));
3480-
}
3481-
else {
3482-
// Variable has initializer that circularly references the variable itself
3483-
type = anyType;
3484-
if (compilerOptions.noImplicitAny) {
3485-
error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer,
3486-
symbolToString(symbol));
3487-
}
3488-
}
3475+
type = reportCircularityError(symbol);
34893476
}
34903477
links.type = type;
34913478
}
@@ -3619,11 +3606,33 @@ namespace ts {
36193606
function getTypeOfInstantiatedSymbol(symbol: Symbol): Type {
36203607
const links = getSymbolLinks(symbol);
36213608
if (!links.type) {
3622-
links.type = instantiateType(getTypeOfSymbol(links.target), links.mapper);
3609+
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
3610+
return unknownType;
3611+
}
3612+
let type = instantiateType(getTypeOfSymbol(links.target), links.mapper);
3613+
if (!popTypeResolution()) {
3614+
type = reportCircularityError(symbol);
3615+
}
3616+
links.type = type;
36233617
}
36243618
return links.type;
36253619
}
36263620

3621+
function reportCircularityError(symbol: Symbol) {
3622+
// Check if variable has type annotation that circularly references the variable itself
3623+
if ((<VariableLikeDeclaration>symbol.valueDeclaration).type) {
3624+
error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation,
3625+
symbolToString(symbol));
3626+
return unknownType;
3627+
}
3628+
// Otherwise variable has initializer that circularly references the variable itself
3629+
if (compilerOptions.noImplicitAny) {
3630+
error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer,
3631+
symbolToString(symbol));
3632+
}
3633+
return anyType;
3634+
}
3635+
36273636
function getTypeOfSymbol(symbol: Symbol): Type {
36283637
if (symbol.flags & SymbolFlags.Instantiated) {
36293638
return getTypeOfInstantiatedSymbol(symbol);
@@ -4667,33 +4676,24 @@ namespace ts {
46674676
* The apparent type of a type parameter is the base constraint instantiated with the type parameter
46684677
* as the type argument for the 'this' type.
46694678
*/
4670-
function getApparentTypeOfTypeParameter(type: TypeParameter) {
4679+
function getApparentTypeOfTypeVariable(type: TypeVariable) {
46714680
if (!type.resolvedApparentType) {
4672-
let constraintType = getConstraintOfTypeParameter(type);
4681+
let constraintType = getConstraintOfTypeVariable(type);
46734682
while (constraintType && constraintType.flags & TypeFlags.TypeParameter) {
4674-
constraintType = getConstraintOfTypeParameter(<TypeParameter>constraintType);
4683+
constraintType = getConstraintOfTypeVariable(<TypeVariable>constraintType);
46754684
}
46764685
type.resolvedApparentType = getTypeWithThisArgument(constraintType || emptyObjectType, type);
46774686
}
46784687
return type.resolvedApparentType;
46794688
}
46804689

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-
46884690
/**
46894691
* For a type parameter, return the base constraint of the type parameter. For the string, number,
46904692
* boolean, and symbol primitive types, return the corresponding object types. Otherwise return the
46914693
* type itself. Note that the apparent type of a union type is the union type itself.
46924694
*/
46934695
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;
4696+
const t = type.flags & TypeFlags.TypeVariable ? getApparentTypeOfTypeVariable(<TypeVariable>type) : type;
46974697
return t.flags & TypeFlags.StringLike ? globalStringType :
46984698
t.flags & TypeFlags.NumberLike ? globalNumberType :
46994699
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
@@ -5279,6 +5279,12 @@ namespace ts {
52795279
return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint;
52805280
}
52815281

5282+
function getConstraintOfTypeVariable(type: TypeVariable): Type {
5283+
return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(<TypeParameter>type) :
5284+
type.flags & TypeFlags.IndexedAccess ? (<IndexedAccessType>type).constraint :
5285+
undefined;
5286+
}
5287+
52825288
function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol {
52835289
return getSymbolOfNode(getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter).parent);
52845290
}
@@ -5954,6 +5960,24 @@ namespace ts {
59545960
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
59555961
type.objectType = objectType;
59565962
type.indexType = indexType;
5963+
// We eagerly compute the constraint of the indexed access type such that circularity
5964+
// errors are immediately caught and reported. For example, class C { x: this["x"] }
5965+
// becomes an error only when the constraint is eagerly computed.
5966+
if (type.objectType.flags & TypeFlags.StructuredType) {
5967+
// The constraint of T[K], where T is an object, union, or intersection type,
5968+
// is the type of the string index signature of T, if any.
5969+
type.constraint = getIndexTypeOfType(type.objectType, IndexKind.String);
5970+
}
5971+
else if (type.objectType.flags & TypeFlags.TypeVariable) {
5972+
// The constraint of T[K], where T is a type variable, is A[K], where A is the
5973+
// apparent type of T.
5974+
const apparentType = getApparentTypeOfTypeVariable(<TypeVariable>type.objectType);
5975+
if (apparentType !== emptyObjectType) {
5976+
type.constraint = isTypeOfKind((<IndexedAccessType>type).indexType, TypeFlags.StringLike) ?
5977+
getIndexedAccessType(apparentType, (<IndexedAccessType>type).indexType) :
5978+
getIndexTypeOfType(apparentType, IndexKind.String);
5979+
}
5980+
}
59575981
return type;
59585982
}
59595983

@@ -6032,14 +6056,19 @@ namespace ts {
60326056
}
60336057

60346058
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) {
6035-
if (maybeTypeOfKind(indexType, TypeFlags.TypeVariable | TypeFlags.Index) || isGenericMappedType(objectType)) {
6059+
// If the index type is generic, if the object type is generic and doesn't originate in an expression,
6060+
// or if the object type is a mapped type with a generic constraint, we are performing a higher-order
6061+
// index access where we cannot meaningfully access the properties of the object type. Note that for a
6062+
// generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to
6063+
// preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved
6064+
// eagerly using the constraint type of 'this' at the given location.
6065+
if (maybeTypeOfKind(indexType, TypeFlags.TypeVariable | TypeFlags.Index) ||
6066+
maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) ||
6067+
isGenericMappedType(objectType)) {
60366068
if (objectType.flags & TypeFlags.Any) {
60376069
return objectType;
60386070
}
6039-
// If the index type is generic or if the object type is a mapped type with a generic constraint,
6040-
// we are performing a higher-order index access where we cannot meaningfully access the properties
6041-
// of the object type. In those cases, we first check that the index type is assignable to 'keyof T'
6042-
// for the object type.
6071+
// We first check that the index type is assignable to 'keyof T' for the object type.
60436072
if (accessNode) {
60446073
if (!isTypeAssignableTo(indexType, getIndexType(objectType))) {
60456074
error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType));
@@ -6056,6 +6085,7 @@ namespace ts {
60566085
const id = objectType.id + "," + indexType.id;
60576086
return indexedAccessTypes[id] || (indexedAccessTypes[id] = createIndexedAccessType(objectType, indexType));
60586087
}
6088+
// In the following we resolve T[K] to the type of the property in T selected by K.
60596089
const apparentObjectType = getApparentType(objectType);
60606090
if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Primitive)) {
60616091
const propTypes: Type[] = [];
@@ -7243,8 +7273,7 @@ namespace ts {
72437273
return result;
72447274
}
72457275
}
7246-
7247-
if (target.flags & TypeFlags.TypeParameter) {
7276+
else if (target.flags & TypeFlags.TypeParameter) {
72487277
// A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P].
72497278
if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>source) === getIndexType(target)) {
72507279
if (!(<MappedType>source).declaration.questionToken) {
@@ -7273,10 +7302,10 @@ namespace ts {
72737302
return result;
72747303
}
72757304
}
7276-
// Given a type parameter T with a constraint C, a type S is assignable to
7305+
// Given a type variable T with a constraint C, a type S is assignable to
72777306
// keyof T if S is assignable to keyof C.
7278-
if ((<IndexType>target).type.flags & TypeFlags.TypeParameter) {
7279-
const constraint = getConstraintOfTypeParameter(<TypeParameter>(<IndexType>target).type);
7307+
if ((<IndexType>target).type.flags & TypeFlags.TypeVariable) {
7308+
const constraint = getConstraintOfTypeVariable(<TypeVariable>(<IndexType>target).type);
72807309
if (constraint) {
72817310
if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) {
72827311
return result;
@@ -7292,6 +7321,14 @@ namespace ts {
72927321
return result;
72937322
}
72947323
}
7324+
// A type S is related to a type T[K] if S is related to A[K], where K is string-like and
7325+
// A is the apparent type of S.
7326+
if ((<IndexedAccessType>target).constraint) {
7327+
if (result = isRelatedTo(source, (<IndexedAccessType>target).constraint, reportErrors)) {
7328+
errorInfo = saveErrorInfo;
7329+
return result;
7330+
}
7331+
}
72957332
}
72967333

72977334
if (source.flags & TypeFlags.TypeParameter) {
@@ -7300,6 +7337,7 @@ namespace ts {
73007337
const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(<MappedType>target));
73017338
const templateType = getTemplateTypeFromMappedType(<MappedType>target);
73027339
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
7340+
errorInfo = saveErrorInfo;
73037341
return result;
73047342
}
73057343
}
@@ -7321,6 +7359,16 @@ namespace ts {
73217359
}
73227360
}
73237361
}
7362+
else if (source.flags & TypeFlags.IndexedAccess) {
7363+
// A type S[K] is related to a type T if A[K] is related to T, where K is string-like and
7364+
// A is the apparent type of S.
7365+
if ((<IndexedAccessType>source).constraint) {
7366+
if (result = isRelatedTo((<IndexedAccessType>source).constraint, target, reportErrors)) {
7367+
errorInfo = saveErrorInfo;
7368+
return result;
7369+
}
7370+
}
7371+
}
73247372
else {
73257373
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
73267374
// We have type references to same target type, see if relationship holds for all type arguments
@@ -14990,8 +15038,8 @@ namespace ts {
1499015038

1499115039
function isLiteralContextualType(contextualType: Type) {
1499215040
if (contextualType) {
14993-
if (contextualType.flags & TypeFlags.TypeParameter) {
14994-
const apparentType = getApparentTypeOfTypeParameter(<TypeParameter>contextualType);
15041+
if (contextualType.flags & TypeFlags.TypeVariable) {
15042+
const apparentType = getApparentTypeOfTypeVariable(<TypeVariable>contextualType);
1499515043
// If the type parameter is constrained to the base primitive type we're checking for,
1499615044
// consider this a literal context. For example, given a type parameter 'T extends string',
1499715045
// this causes us to infer string literal types for T.
@@ -15826,7 +15874,7 @@ namespace ts {
1582615874
checkSourceElement(node.type);
1582715875
const type = <MappedType>getTypeFromMappedTypeNode(node);
1582815876
const constraintType = getConstraintTypeFromMappedType(type);
15829-
const keyType = constraintType.flags & TypeFlags.TypeParameter ? getApparentTypeOfTypeParameter(<TypeParameter>constraintType) : constraintType;
15877+
const keyType = constraintType.flags & TypeFlags.TypeVariable ? getApparentTypeOfTypeVariable(<TypeVariable>constraintType) : constraintType;
1583015878
checkTypeAssignableTo(keyType, stringType, node.typeParameter.constraint);
1583115879
}
1583215880

src/compiler/types.ts

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

29702970
export interface TypeVariable extends Type {
2971+
/* @internal */
2972+
resolvedApparentType: Type;
29712973
/* @internal */
29722974
resolvedIndexType: IndexType;
29732975
}
@@ -2980,8 +2982,6 @@ namespace ts {
29802982
/* @internal */
29812983
mapper?: TypeMapper; // Instantiation mapper
29822984
/* @internal */
2983-
resolvedApparentType: Type;
2984-
/* @internal */
29852985
isThisType?: boolean;
29862986
}
29872987

@@ -2990,6 +2990,7 @@ namespace ts {
29902990
export interface IndexedAccessType extends TypeVariable {
29912991
objectType: Type;
29922992
indexType: Type;
2993+
constraint?: Type;
29932994
}
29942995

29952996
// keyof T types (TypeFlags.Index)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
tests/cases/conformance/types/keyof/circularIndexedAccessErrors.ts(3,5): error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
2+
tests/cases/conformance/types/keyof/circularIndexedAccessErrors.ts(7,5): error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
3+
tests/cases/conformance/types/keyof/circularIndexedAccessErrors.ts(15,5): error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
4+
tests/cases/conformance/types/keyof/circularIndexedAccessErrors.ts(19,5): error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
5+
tests/cases/conformance/types/keyof/circularIndexedAccessErrors.ts(23,5): error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
6+
tests/cases/conformance/types/keyof/circularIndexedAccessErrors.ts(27,5): error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
7+
tests/cases/conformance/types/keyof/circularIndexedAccessErrors.ts(28,5): error TS2502: 'y' is referenced directly or indirectly in its own type annotation.
8+
tests/cases/conformance/types/keyof/circularIndexedAccessErrors.ts(29,5): error TS2502: 'z' is referenced directly or indirectly in its own type annotation.
9+
10+
11+
==== tests/cases/conformance/types/keyof/circularIndexedAccessErrors.ts (8 errors) ====
12+
13+
type T1 = {
14+
x: T1["x"]; // Error
15+
~~~~~~~~~~~
16+
!!! error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
17+
};
18+
19+
type T2<K extends "x" | "y"> = {
20+
x: T2<K>[K]; // Error
21+
~~~~~~~~~~~~
22+
!!! error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
23+
y: number;
24+
}
25+
26+
declare let x2: T2<"x">;
27+
let x2x = x2.x;
28+
29+
interface T3<T extends T3<T>> {
30+
x: T["x"]; // Error
31+
~~~~~~~~~~
32+
!!! error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
33+
}
34+
35+
interface T4<T extends T4<T>> {
36+
x: T4<T>["x"]; // Error
37+
~~~~~~~~~~~~~~
38+
!!! error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
39+
}
40+
41+
class C1 {
42+
x: C1["x"]; // Error
43+
~~~~~~~~~~~
44+
!!! error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
45+
}
46+
47+
class C2 {
48+
x: this["y"]; // Error
49+
~~~~~~~~~~~~~
50+
!!! error TS2502: 'x' is referenced directly or indirectly in its own type annotation.
51+
y: this["z"]; // Error
52+
~~~~~~~~~~~~~
53+
!!! error TS2502: 'y' is referenced directly or indirectly in its own type annotation.
54+
z: this["x"]; // Error
55+
~~~~~~~~~~~~~
56+
!!! error TS2502: 'z' is referenced directly or indirectly in its own type annotation.
57+
}

0 commit comments

Comments
 (0)