Skip to content

Commit 0f748a9

Browse files
committed
Add writable indexed access types
1 parent 25f609b commit 0f748a9

9 files changed

+697
-57
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7890,7 +7890,7 @@ namespace ts {
78907890
}
78917891

78927892
function getSimplifiedTypeOrConstraint(type: Type) {
7893-
const simplified = getSimplifiedType(type, /*writing*/ false);
7893+
const simplified = getSimplifiedType(type);
78947894
return simplified !== type ? simplified : getConstraintOfType(type);
78957895
}
78967896

@@ -7935,7 +7935,7 @@ namespace ts {
79357935
// a union - once negated types exist and are applied to the conditional false branch, this "constraint"
79367936
// likely doesn't need to exist.
79377937
if (type.root.isDistributive && type.restrictiveInstantiation !== type) {
7938-
const simplified = getSimplifiedType(type.checkType, /*writing*/ false);
7938+
const simplified = getSimplifiedType(type.checkType);
79397939
const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified;
79407940
if (constraint && constraint !== type.checkType) {
79417941
const mapper = makeUnaryTypeMapper(type.root.checkType, constraint);
@@ -8037,7 +8037,7 @@ namespace ts {
80378037
return t.immediateBaseConstraint = noConstraintType;
80388038
}
80398039
constraintDepth++;
8040-
let result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false));
8040+
let result = computeBaseConstraint(getSimplifiedType(t));
80418041
constraintDepth--;
80428042
if (!popTypeResolution()) {
80438043
if (t.flags & TypeFlags.TypeParameter) {
@@ -10253,9 +10253,23 @@ namespace ts {
1025310253
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
1025410254
type.objectType = objectType;
1025510255
type.indexType = indexType;
10256+
type.writing = false;
1025610257
return type;
1025710258
}
1025810259

10260+
function getWritingIndexedAccessType(type: IndexedAccessType) {
10261+
const id = "w," + type.objectType.id + "," + type.indexType.id;
10262+
let writeType = indexedAccessTypes.get(id);
10263+
if (!writeType) {
10264+
writeType = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
10265+
writeType.objectType = type.objectType;
10266+
writeType.indexType = type.indexType;
10267+
writeType.writing = true;
10268+
indexedAccessTypes.set(id, writeType);
10269+
}
10270+
return writeType;
10271+
}
10272+
1025910273
/**
1026010274
* Returns if a type is or consists of a JSLiteral object type
1026110275
* In addition to objects which are directly literals,
@@ -10462,9 +10476,9 @@ namespace ts {
1046210476
return !!(type.flags & TypeFlags.TypeParameter && (<TypeParameter>type).isThisType);
1046310477
}
1046410478

10465-
function getSimplifiedType(type: Type, writing: boolean): Type {
10466-
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type, writing) :
10467-
type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(<ConditionalType>type, writing) :
10479+
function getSimplifiedType(type: Type): Type {
10480+
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type) :
10481+
type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(<ConditionalType>type) :
1046810482
type;
1046910483
}
1047010484

@@ -10473,7 +10487,7 @@ namespace ts {
1047310487
// (T | U)[K] -> T[K] & U[K] (writing)
1047410488
// (T & U)[K] -> T[K] & U[K]
1047510489
if (objectType.flags & TypeFlags.UnionOrIntersection) {
10476-
const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing));
10490+
const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType)));
1047710491
return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types);
1047810492
}
1047910493
}
@@ -10482,24 +10496,25 @@ namespace ts {
1048210496
// T[A | B] -> T[A] | T[B] (reading)
1048310497
// T[A | B] -> T[A] & T[B] (writing)
1048410498
if (indexType.flags & TypeFlags.Union) {
10485-
const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing));
10499+
const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t)));
1048610500
return writing ? getIntersectionType(types) : getUnionType(types);
1048710501
}
1048810502
}
1048910503

1049010504
// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
1049110505
// the type itself if no transformation is possible. The writing flag indicates that the type is
1049210506
// the target of an assignment.
10493-
function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): Type {
10507+
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
10508+
const writing = type.writing;
1049410509
const cache = writing ? "simplifiedForWriting" : "simplifiedForReading";
1049510510
if (type[cache]) {
1049610511
return type[cache] === circularConstraintType ? type : type[cache]!;
1049710512
}
1049810513
type[cache] = circularConstraintType;
1049910514
// We recursively simplify the object type as it may in turn be an indexed access type. For example, with
1050010515
// '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type.
10501-
const objectType = getSimplifiedType(type.objectType, writing);
10502-
const indexType = getSimplifiedType(type.indexType, writing);
10516+
const objectType = getSimplifiedType(type.objectType);
10517+
const indexType = getSimplifiedType(type.indexType);
1050310518
// T[A | B] -> T[A] | T[B] (reading)
1050410519
// T[A | B] -> T[A] & T[B] (writing)
1050510520
const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing);
@@ -10523,20 +10538,20 @@ namespace ts {
1052310538
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
1052410539
// construct the type Box<T[X]>.
1052510540
if (isGenericMappedType(objectType)) {
10526-
return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing));
10541+
return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t));
1052710542
}
1052810543
return type[cache] = type;
1052910544
}
1053010545

10531-
function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) {
10546+
function getSimplifiedConditionalType(type: ConditionalType) {
1053210547
const checkType = type.checkType;
1053310548
const extendsType = type.extendsType;
1053410549
const trueType = getTrueTypeFromConditionalType(type);
1053510550
const falseType = getFalseTypeFromConditionalType(type);
1053610551
// Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`.
1053710552
if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) {
1053810553
if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
10539-
return getSimplifiedType(trueType, writing);
10554+
return getSimplifiedType(trueType);
1054010555
}
1054110556
else if (isIntersectionEmpty(checkType, extendsType)) { // Always false
1054210557
return neverType;
@@ -10547,7 +10562,7 @@ namespace ts {
1054710562
return neverType;
1054810563
}
1054910564
else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false
10550-
return getSimplifiedType(falseType, writing);
10565+
return getSimplifiedType(falseType);
1055110566
}
1055210567
}
1055310568
return type;
@@ -10589,7 +10604,7 @@ namespace ts {
1058910604
return objectType;
1059010605
}
1059110606
// Defer the operation by creating an indexed access type.
10592-
const id = objectType.id + "," + indexType.id;
10607+
const id = "r," + objectType.id + "," + indexType.id;
1059310608
let type = indexedAccessTypes.get(id);
1059410609
if (!type) {
1059510610
indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType));
@@ -12812,10 +12827,10 @@ namespace ts {
1281212827
target = (<SubstitutionType>target).typeVariable;
1281312828
}
1281412829
if (source.flags & TypeFlags.Simplifiable) {
12815-
source = getSimplifiedType(source, /*writing*/ false);
12830+
source = getSimplifiedType(source);
1281612831
}
1281712832
if (target.flags & TypeFlags.Simplifiable) {
12818-
target = getSimplifiedType(target, /*writing*/ true);
12833+
target = getSimplifiedType(target);
1281912834
}
1282012835

1282112836
// Try to see if we're relating something like `Foo` -> `Bar | null | undefined`.
@@ -15653,16 +15668,16 @@ namespace ts {
1565315668
}
1565415669
else {
1565515670
// Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
15656-
const simplified = getSimplifiedType(target, /*writing*/ false);
15671+
const simplified = getSimplifiedType(target);
1565715672
if (simplified !== target) {
1565815673
invokeOnce(source, simplified, inferFromTypes);
1565915674
}
1566015675
else if (target.flags & TypeFlags.IndexedAccess) {
15661-
const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false);
15676+
const indexType = getSimplifiedType((target as IndexedAccessType).indexType);
1566215677
// Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider
1566315678
// that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can.
1566415679
if (indexType.flags & TypeFlags.Instantiable) {
15665-
const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false);
15680+
const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType), indexType, (target as IndexedAccessType).writing);
1566615681
if (simplified && simplified !== target) {
1566715682
invokeOnce(source, simplified, inferFromTypes);
1566815683
}
@@ -24738,6 +24753,7 @@ namespace ts {
2473824753
if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access)
2473924754
&& (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) {
2474024755
// to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported
24756+
leftType = leftType.flags & TypeFlags.IndexedAccess ? getWritingIndexedAccessType(<IndexedAccessType>leftType) : leftType;
2474124757
checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right);
2474224758
}
2474324759
}

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4375,6 +4375,7 @@ namespace ts {
43754375
export interface IndexedAccessType extends InstantiableType {
43764376
objectType: Type;
43774377
indexType: Type;
4378+
writing: boolean;
43784379
constraint?: Type;
43794380
simplifiedForReading?: Type;
43804381
simplifiedForWriting?: Type;

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2383,6 +2383,7 @@ declare namespace ts {
23832383
export interface IndexedAccessType extends InstantiableType {
23842384
objectType: Type;
23852385
indexType: Type;
2386+
writing: boolean;
23862387
constraint?: Type;
23872388
simplifiedForReading?: Type;
23882389
simplifiedForWriting?: Type;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2383,6 +2383,7 @@ declare namespace ts {
23832383
export interface IndexedAccessType extends InstantiableType {
23842384
objectType: Type;
23852385
indexType: Type;
2386+
writing: boolean;
23862387
constraint?: Type;
23872388
simplifiedForReading?: Type;
23882389
simplifiedForWriting?: Type;

0 commit comments

Comments
 (0)