Skip to content

Commit c344e6d

Browse files
committed
Fixes and improvements to indexed access type relationships
1 parent b746f8e commit c344e6d

File tree

2 files changed

+67
-65
lines changed

2 files changed

+67
-65
lines changed

src/compiler/checker.ts

+66-64
Original file line numberDiff line numberDiff line change
@@ -6268,18 +6268,10 @@ namespace ts {
62686268
}
62696269

62706270
function getConstraintOfIndexedAccess(type: IndexedAccessType) {
6271-
const transformed = getSimplifiedIndexedAccessType(type);
6272-
if (transformed) {
6273-
return transformed;
6274-
}
6275-
const baseObjectType = getBaseConstraintOfType(type.objectType);
6276-
const baseIndexType = getBaseConstraintOfType(type.indexType);
6277-
if (baseIndexType === stringType && !getIndexInfoOfType(baseObjectType || type.objectType, IndexKind.String)) {
6278-
// getIndexedAccessType returns `any` for X[string] where X doesn't have an index signature.
6279-
// to avoid this, return `undefined`.
6280-
return undefined;
6281-
}
6282-
return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined;
6271+
const objectType = getBaseConstraintOfType(type.objectType) || type.objectType;
6272+
const indexType = getBaseConstraintOfType(type.indexType) || type.indexType;
6273+
const constraint = !isGenericObjectType(objectType) && !isGenericIndexType(indexType) ? getIndexedAccessType(objectType, indexType) : undefined;
6274+
return constraint && constraint !== unknownType ? constraint : undefined;
62836275
}
62846276

62856277
function getDefaultConstraintOfConditionalType(type: ConditionalType) {
@@ -6326,7 +6318,7 @@ namespace ts {
63266318
function getBaseConstraintOfType(type: Type): Type {
63276319
const constraint = getBaseConstraintOfInstantiableNonPrimitiveUnionOrIntersection(type);
63286320
if (!constraint && type.flags & TypeFlags.Index) {
6329-
return stringType;
6321+
return keyofConstraintType;
63306322
}
63316323
return constraint;
63326324
}
@@ -6361,7 +6353,7 @@ namespace ts {
63616353
circular = true;
63626354
return undefined;
63636355
}
6364-
const result = computeBaseConstraint(t);
6356+
const result = computeBaseConstraint(getSimplifiedType(t));
63656357
if (!popTypeResolution()) {
63666358
circular = true;
63676359
return undefined;
@@ -6390,13 +6382,9 @@ namespace ts {
63906382
undefined;
63916383
}
63926384
if (t.flags & TypeFlags.Index) {
6393-
return stringType;
6385+
return keyofConstraintType;
63946386
}
63956387
if (t.flags & TypeFlags.IndexedAccess) {
6396-
const transformed = getSimplifiedIndexedAccessType(<IndexedAccessType>t);
6397-
if (transformed) {
6398-
return getBaseConstraint(transformed);
6399-
}
64006388
const baseObjectType = getBaseConstraint((<IndexedAccessType>t).objectType);
64016389
const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
64026390
const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined;
@@ -6520,26 +6508,30 @@ namespace ts {
65206508
if (props.length === 1 && !(checkFlags & CheckFlags.Partial)) {
65216509
return props[0];
65226510
}
6523-
const propTypes: Type[] = [];
6524-
const declarations: Declaration[] = [];
6511+
let declarations: Declaration[];
65256512
let commonType: Type;
6513+
let nameType: Type;
6514+
let propTypes: Type[] = [];
6515+
let first = true;
65266516
for (const prop of props) {
6527-
if (prop.declarations) {
6528-
addRange(declarations, prop.declarations);
6529-
}
6517+
declarations = addRange(declarations, prop.declarations);
65306518
const type = getTypeOfSymbol(prop);
6531-
if (!commonType) {
6519+
if (first) {
65326520
commonType = type;
6521+
nameType = prop.nameType;
6522+
first = false;
65336523
}
6534-
else if (type !== commonType) {
6524+
else {
6525+
if (type !== commonType) {
65356526
checkFlags |= CheckFlags.HasNonUniformType;
65366527
}
6528+
}
65376529
propTypes.push(type);
65386530
}
6539-
// !!!
65406531
const result = createSymbol(SymbolFlags.Property | commonFlags, name, syntheticFlag | checkFlags);
65416532
result.containingType = containingType;
65426533
result.declarations = declarations;
6534+
result.nameType = nameType;
65436535
result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes);
65446536
return result;
65456537
}
@@ -8151,9 +8143,8 @@ namespace ts {
81518143
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type) :
81528144
getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
81538145
type === wildcardType ? wildcardType :
8154-
type.flags & TypeFlags.Any ? keyofConstraintType :
8155-
keyofStringsOnly ? getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral) :
8156-
getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromPropertyNames(type, TypeFlags.UniqueESSymbol)]) :
8146+
type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? keyofConstraintType :
8147+
keyofStringsOnly ? getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral) :
81578148
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
81588149
getLiteralTypeFromPropertyNames(type, TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.UniqueESSymbol);
81598150
}
@@ -8256,9 +8247,8 @@ namespace ts {
82568247
else {
82578248
error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType));
82588249
}
8259-
return unknownType;
82608250
}
8261-
return anyType;
8251+
return unknownType;
82628252
}
82638253

82648254
function isGenericObjectType(type: Type): boolean {
@@ -8285,8 +8275,12 @@ namespace ts {
82858275
return getObjectFlags(type) & ObjectFlags.Mapped && getTemplateTypeFromMappedType(type as MappedType) === neverType;
82868276
}
82878277

8278+
function getSimplifiedType(type: Type): Type {
8279+
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type) : type;
8280+
}
8281+
82888282
// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
8289-
// undefined if no transformation is possible.
8283+
// the type itself if no transformation is possible.
82908284
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
82918285
const objectType = type.objectType;
82928286
if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType)) {
@@ -8306,7 +8300,7 @@ namespace ts {
83068300
}
83078301
}
83088302
return getUnionType([
8309-
getIndexedAccessType(getIntersectionType(regularTypes), type.indexType),
8303+
getSimplifiedType(getIndexedAccessType(getIntersectionType(regularTypes), type.indexType)),
83108304
getIntersectionType(stringIndexTypes)
83118305
]);
83128306
}
@@ -8316,13 +8310,13 @@ namespace ts {
83168310
// eventually anyway, but it easier to reason about.
83178311
if (some((<IntersectionType>objectType).types, isMappedTypeToNever)) {
83188312
const nonNeverTypes = filter((<IntersectionType>objectType).types, t => !isMappedTypeToNever(t));
8319-
return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType);
8313+
return getSimplifiedType(getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType));
83208314
}
83218315
}
8322-
83238316
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
83248317
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
8325-
// construct the type Box<T[X]>.
8318+
// construct the type Box<T[X]>. We do not further simplify the result because mapped types can be recursive
8319+
// and we might never terminate.
83268320
if (isGenericMappedType(objectType)) {
83278321
return substituteIndexedMappedType(objectType, type);
83288322
}
@@ -8332,7 +8326,7 @@ namespace ts {
83328326
return substituteIndexedMappedType(constraint, type);
83338327
}
83348328
}
8335-
return undefined;
8329+
return type;
83368330
}
83378331

83388332
function substituteIndexedMappedType(objectType: MappedType, type: IndexedAccessType) {
@@ -9887,6 +9881,12 @@ namespace ts {
98879881
if (target.flags & TypeFlags.Substitution) {
98889882
target = (<SubstitutionType>target).typeVariable;
98899883
}
9884+
if (source.flags & TypeFlags.IndexedAccess) {
9885+
source = getSimplifiedType(source);
9886+
}
9887+
if (target.flags & TypeFlags.IndexedAccess) {
9888+
target = getSimplifiedType(target);
9889+
}
98909890

98919891
// both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases
98929892
if (source === target) return Ternary.True;
@@ -10346,9 +10346,9 @@ namespace ts {
1034610346
}
1034710347
}
1034810348
else if (target.flags & TypeFlags.IndexedAccess) {
10349-
// A type S is related to a type T[K] if S is related to A[K], where K is string-like and
10350-
// A is the apparent type of T.
10351-
const constraint = getConstraintForRelation(<IndexedAccessType>target);
10349+
// A type S is related to a type T[K] if S is related to C, where C is the
10350+
// constraint of T[K]
10351+
const constraint = getConstraintForRelation(target);
1035210352
if (constraint) {
1035310353
if (result = isRelatedTo(source, constraint, reportErrors)) {
1035410354
errorInfo = saveErrorInfo;
@@ -10361,21 +10361,21 @@ namespace ts {
1036110361
const template = getTemplateTypeFromMappedType(target);
1036210362
const modifiers = getMappedTypeModifiers(target);
1036310363
if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) {
10364-
if (template.flags & TypeFlags.IndexedAccess && (<IndexedAccessType>template).objectType === source &&
10365-
(<IndexedAccessType>template).indexType === getTypeParameterFromMappedType(target)) {
10366-
return Ternary.True;
10367-
}
10368-
// A source type T is related to a target type { [P in keyof T]: X } if T[P] is related to X.
10369-
if (!isGenericMappedType(source) && getConstraintTypeFromMappedType(target) === getIndexType(source)) {
10370-
const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(target));
10371-
const templateType = getTemplateTypeFromMappedType(target);
10372-
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
10373-
errorInfo = saveErrorInfo;
10374-
return result;
10364+
if (template.flags & TypeFlags.IndexedAccess && (<IndexedAccessType>template).objectType === source &&
10365+
(<IndexedAccessType>template).indexType === getTypeParameterFromMappedType(target)) {
10366+
return Ternary.True;
10367+
}
10368+
// A source type T is related to a target type { [P in keyof T]: X } if T[P] is related to X.
10369+
if (!isGenericMappedType(source) && getConstraintTypeFromMappedType(target) === getIndexType(source)) {
10370+
const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(target));
10371+
const templateType = getTemplateTypeFromMappedType(target);
10372+
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
10373+
errorInfo = saveErrorInfo;
10374+
return result;
10375+
}
1037510376
}
1037610377
}
1037710378
}
10378-
}
1037910379

1038010380
if (source.flags & TypeFlags.TypeParameter) {
1038110381
let constraint = getConstraintForRelation(<TypeParameter>source);
@@ -10393,16 +10393,8 @@ namespace ts {
1039310393
}
1039410394
}
1039510395
else if (source.flags & TypeFlags.IndexedAccess) {
10396-
// A type S[K] is related to a type T if A[K] is related to T, where K is string-like and
10397-
// A is the apparent type of S.
10398-
const constraint = getConstraintForRelation(<IndexedAccessType>source);
10399-
if (constraint) {
10400-
if (result = isRelatedTo(constraint, target, reportErrors)) {
10401-
errorInfo = saveErrorInfo;
10402-
return result;
10403-
}
10404-
}
10405-
else if (target.flags & TypeFlags.IndexedAccess) {
10396+
if (target.flags & TypeFlags.IndexedAccess) {
10397+
// A type S[K] is related to a type T[J] if S is related to T and K is related to J.
1040610398
if (result = isRelatedTo((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType, reportErrors)) {
1040710399
result &= isRelatedTo((<IndexedAccessType>source).indexType, (<IndexedAccessType>target).indexType, reportErrors);
1040810400
}
@@ -10411,6 +10403,15 @@ namespace ts {
1041110403
return result;
1041210404
}
1041310405
}
10406+
// A type S[K] is related to a type T if C is related to T, where C is the
10407+
// constraint of S[K].
10408+
const constraint = getConstraintForRelation(<IndexedAccessType>source);
10409+
if (constraint) {
10410+
if (result = isRelatedTo(constraint, target, reportErrors)) {
10411+
errorInfo = saveErrorInfo;
10412+
return result;
10413+
}
10414+
}
1041410415
}
1041510416
else if (source.flags & TypeFlags.Index) {
1041610417
if (result = isRelatedTo(keyofConstraintType, target, reportErrors)) {
@@ -19917,7 +19918,8 @@ namespace ts {
1991719918
// this a literal context for literals of that primitive type. For example, given a
1991819919
// type parameter 'T extends string', infer string literal types for T.
1991919920
const constraint = getBaseConstraintOfType(contextualType) || emptyObjectType;
19920-
return constraint.flags & TypeFlags.String && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
19921+
return constraint === keyofConstraintType && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.UniqueESSymbol) ||
19922+
constraint.flags & TypeFlags.String && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) ||
1992119923
constraint.flags & TypeFlags.Number && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) ||
1992219924
constraint.flags & TypeFlags.Boolean && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) ||
1992319925
constraint.flags & TypeFlags.ESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) ||

src/server/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ namespace ts.server {
8686
export function mergeMapLikes<T extends object>(target: T, source: Partial<T>): void {
8787
for (const key in source) {
8888
if (hasProperty(source, key)) {
89-
target[key] = source[key];
89+
target[key] = (<T>source)[key];
9090
}
9191
}
9292
}

0 commit comments

Comments
 (0)