@@ -5788,8 +5788,7 @@ namespace ts {
57885788 }
57895789
57905790 function isGenericMappedType(type: Type) {
5791- return getObjectFlags(type) & ObjectFlags.Mapped &&
5792- maybeTypeOfKind(getConstraintTypeFromMappedType(<MappedType>type), TypeFlags.TypeVariable | TypeFlags.Index);
5791+ return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(<MappedType>type));
57935792 }
57945793
57955794 function resolveStructuredTypeMembers(type: StructuredType): ResolvedType {
@@ -5901,6 +5900,10 @@ namespace ts {
59015900 }
59025901
59035902 function getConstraintOfIndexedAccess(type: IndexedAccessType) {
5903+ const transformed = getTransformedIndexedAccessType(type);
5904+ if (transformed) {
5905+ return transformed;
5906+ }
59045907 const baseObjectType = getBaseConstraintOfType(type.objectType);
59055908 const baseIndexType = getBaseConstraintOfType(type.indexType);
59065909 return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined;
@@ -5972,11 +5975,18 @@ namespace ts {
59725975 return stringType;
59735976 }
59745977 if (t.flags & TypeFlags.IndexedAccess) {
5978+ const transformed = getTransformedIndexedAccessType(<IndexedAccessType>t);
5979+ if (transformed) {
5980+ return getBaseConstraint(transformed);
5981+ }
59755982 const baseObjectType = getBaseConstraint((<IndexedAccessType>t).objectType);
59765983 const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
59775984 const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined;
59785985 return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined;
59795986 }
5987+ if (isGenericMappedType(t)) {
5988+ return emptyObjectType;
5989+ }
59805990 return t;
59815991 }
59825992 }
@@ -7604,26 +7614,73 @@ namespace ts {
76047614 return instantiateType(getTemplateTypeFromMappedType(type), templateMapper);
76057615 }
76067616
7607- function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) {
7608- // If the index type is generic, if the object type is generic and doesn't originate in an expression,
7609- // or if the object type is a mapped type with a generic constraint, we are performing a higher-order
7610- // index access where we cannot meaningfully access the properties of the object type. Note that for a
7611- // generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to
7612- // preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved
7613- // eagerly using the constraint type of 'this' at the given location.
7614- if (maybeTypeOfKind(indexType, TypeFlags.TypeVariable | TypeFlags.Index) ||
7615- maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) ||
7616- isGenericMappedType(objectType)) {
7617+ function isGenericObjectType(type: Type): boolean {
7618+ return type.flags & TypeFlags.TypeVariable ? true :
7619+ getObjectFlags(type) & ObjectFlags.Mapped ? isGenericIndexType(getConstraintTypeFromMappedType(<MappedType>type)) :
7620+ type.flags & TypeFlags.UnionOrIntersection ? forEach((<UnionOrIntersectionType>type).types, isGenericObjectType) :
7621+ false;
7622+ }
7623+
7624+ function isGenericIndexType(type: Type): boolean {
7625+ return type.flags & (TypeFlags.TypeVariable | TypeFlags.Index) ? true :
7626+ type.flags & TypeFlags.UnionOrIntersection ? forEach((<UnionOrIntersectionType>type).types, isGenericIndexType) :
7627+ false;
7628+ }
7629+
7630+ // Return true if the given type is a non-generic object type with a string index signature and no
7631+ // other members.
7632+ function isStringIndexOnlyType(type: Type) {
7633+ if (type.flags & TypeFlags.Object && !isGenericMappedType(type)) {
7634+ const t = resolveStructuredTypeMembers(<ObjectType>type);
7635+ return t.properties.length === 0 &&
7636+ t.callSignatures.length === 0 && t.constructSignatures.length === 0 &&
7637+ t.stringIndexInfo && !t.numberIndexInfo;
7638+ }
7639+ return false;
7640+ }
7641+
7642+ // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
7643+ // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a
7644+ // transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed
7645+ // access types with default property values as expressed by D.
7646+ function getTransformedIndexedAccessType(type: IndexedAccessType): Type {
7647+ const objectType = type.objectType;
7648+ if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((<IntersectionType>objectType).types, isStringIndexOnlyType)) {
7649+ const regularTypes: Type[] = [];
7650+ const stringIndexTypes: Type[] = [];
7651+ for (const t of (<IntersectionType>objectType).types) {
7652+ if (isStringIndexOnlyType(t)) {
7653+ stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String));
7654+ }
7655+ else {
7656+ regularTypes.push(t);
7657+ }
7658+ }
7659+ return getUnionType([
7660+ getIndexedAccessType(getIntersectionType(regularTypes), type.indexType),
7661+ getIntersectionType(stringIndexTypes)
7662+ ]);
7663+ }
7664+ return undefined;
7665+ }
7666+
7667+ function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type {
7668+ // If the object type is a mapped type { [P in K]: E }, where K is generic, we instantiate E using a mapper
7669+ // that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
7670+ // construct the type Box<T[X]>.
7671+ if (isGenericMappedType(objectType)) {
7672+ return getIndexedAccessForMappedType(<MappedType>objectType, indexType, accessNode);
7673+ }
7674+ // Otherwise, if the index type is generic, or if the object type is generic and doesn't originate in an
7675+ // expression, we are performing a higher-order index access where we cannot meaningfully access the properties
7676+ // of the object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates
7677+ // in an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]'
7678+ // has always been resolved eagerly using the constraint type of 'this' at the given location.
7679+ if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) && isGenericObjectType(objectType)) {
76177680 if (objectType.flags & TypeFlags.Any) {
76187681 return objectType;
76197682 }
7620- // If the object type is a mapped type { [P in K]: E }, we instantiate E using a mapper that substitutes
7621- // the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we construct the
7622- // type Box<T[X]>.
7623- if (isGenericMappedType(objectType)) {
7624- return getIndexedAccessForMappedType(<MappedType>objectType, indexType, accessNode);
7625- }
7626- // Otherwise we defer the operation by creating an indexed access type.
7683+ // Defer the operation by creating an indexed access type.
76277684 const id = objectType.id + "," + indexType.id;
76287685 let type = indexedAccessTypes.get(id);
76297686 if (!type) {
@@ -8017,7 +8074,7 @@ namespace ts {
80178074
80188075 function cloneTypeMapper(mapper: TypeMapper): TypeMapper {
80198076 return mapper && isInferenceContext(mapper) ?
8020- createInferenceContext(mapper.signature, mapper.flags | InferenceFlags.NoDefault, mapper.inferences) :
8077+ createInferenceContext(mapper.signature, mapper.flags | InferenceFlags.NoDefault, mapper.compareTypes, mapper. inferences) :
80218078 mapper;
80228079 }
80238080
@@ -8458,7 +8515,7 @@ namespace ts {
84588515 ignoreReturnTypes: boolean,
84598516 reportErrors: boolean,
84608517 errorReporter: ErrorReporter,
8461- compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary ): Ternary {
8518+ compareTypes: TypeComparer ): Ternary {
84628519 // TODO (drosen): De-duplicate code between related functions.
84638520 if (source === target) {
84648521 return Ternary.True;
@@ -8468,7 +8525,7 @@ namespace ts {
84688525 }
84698526
84708527 if (source.typeParameters) {
8471- source = instantiateSignatureInContextOf(source, target);
8528+ source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes );
84728529 }
84738530
84748531 let result = Ternary.True;
@@ -9614,6 +9671,11 @@ namespace ts {
96149671 if (sourceInfo) {
96159672 return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors);
96169673 }
9674+ if (isGenericMappedType(source)) {
9675+ // A generic mapped type { [P in K]: T } is related to an index signature { [x: string]: U }
9676+ // if T is related to U.
9677+ return kind === IndexKind.String && isRelatedTo(getTemplateTypeFromMappedType(<MappedType>source), targetInfo.type, reportErrors);
9678+ }
96179679 if (isObjectLiteralType(source)) {
96189680 let related = Ternary.True;
96199681 if (kind === IndexKind.String) {
@@ -10216,13 +10278,14 @@ namespace ts {
1021610278 }
1021710279 }
1021810280
10219- function createInferenceContext(signature: Signature, flags: InferenceFlags, baseInferences?: InferenceInfo[]): InferenceContext {
10281+ function createInferenceContext(signature: Signature, flags: InferenceFlags, compareTypes?: TypeComparer, baseInferences?: InferenceInfo[]): InferenceContext {
1022010282 const inferences = baseInferences ? map(baseInferences, cloneInferenceInfo) : map(signature.typeParameters, createInferenceInfo);
1022110283 const context = mapper as InferenceContext;
1022210284 context.mappedTypes = signature.typeParameters;
1022310285 context.signature = signature;
1022410286 context.inferences = inferences;
1022510287 context.flags = flags;
10288+ context.compareTypes = compareTypes || compareTypesAssignable;
1022610289 return context;
1022710290
1022810291 function mapper(t: Type): Type {
@@ -10325,6 +10388,19 @@ namespace ts {
1032510388 }
1032610389 }
1032710390
10391+ function isPossiblyAssignableTo(source: Type, target: Type) {
10392+ const properties = getPropertiesOfObjectType(target);
10393+ for (const targetProp of properties) {
10394+ if (!(targetProp.flags & (SymbolFlags.Optional | SymbolFlags.Prototype))) {
10395+ const sourceProp = getPropertyOfObjectType(source, targetProp.escapedName);
10396+ if (!sourceProp) {
10397+ return false;
10398+ }
10399+ }
10400+ }
10401+ return true;
10402+ }
10403+
1032810404 function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) {
1032910405 let symbolStack: Symbol[];
1033010406 let visited: Map<boolean>;
@@ -10518,10 +10594,14 @@ namespace ts {
1051810594 return;
1051910595 }
1052010596 }
10521- inferFromProperties(source, target);
10522- inferFromSignatures(source, target, SignatureKind.Call);
10523- inferFromSignatures(source, target, SignatureKind.Construct);
10524- inferFromIndexTypes(source, target);
10597+ // Infer from the members of source and target only if the two types are possibly related. We check
10598+ // in both directions because we may be inferring for a co-variant or a contra-variant position.
10599+ if (isPossiblyAssignableTo(source, target) || isPossiblyAssignableTo(target, source)) {
10600+ inferFromProperties(source, target);
10601+ inferFromSignatures(source, target, SignatureKind.Call);
10602+ inferFromSignatures(source, target, SignatureKind.Construct);
10603+ inferFromIndexTypes(source, target);
10604+ }
1052510605 }
1052610606
1052710607 function inferFromProperties(source: Type, target: Type) {
@@ -10653,7 +10733,7 @@ namespace ts {
1065310733 const constraint = getConstraintOfTypeParameter(context.signature.typeParameters[index]);
1065410734 if (constraint) {
1065510735 const instantiatedConstraint = instantiateType(constraint, context);
10656- if (!isTypeAssignableTo (inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
10736+ if (!context.compareTypes (inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
1065710737 inference.inferredType = inferredType = instantiatedConstraint;
1065810738 }
1065910739 }
@@ -15054,8 +15134,8 @@ namespace ts {
1505415134 }
1505515135
1505615136 // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec)
15057- function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper?: TypeMapper): Signature {
15058- const context = createInferenceContext(signature, InferenceFlags.InferUnionTypes);
15137+ function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper?: TypeMapper, compareTypes?: TypeComparer ): Signature {
15138+ const context = createInferenceContext(signature, InferenceFlags.InferUnionTypes, compareTypes );
1505915139 forEachMatchingParameterType(contextualSignature, signature, (source, target) => {
1506015140 // Type parameters from outer context referenced by source type are fixed by instantiation of the source type
1506115141 inferTypes(context.inferences, instantiateType(source, contextualMapper || identityMapper), target);
0 commit comments