diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9c87e949615d..61607e9c90400 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14887,10 +14887,22 @@ namespace ts { return type; } + // We consider a type to be partially inferable if it isn't marked non-inferable or if it is + // an object literal type with at least one property of an inferable type. For example, an object + // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive + // arrow function, but is considered partially inferable because property 'a' has an inferable type. + function isPartiallyInferableType(type: Type): boolean { + return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || + isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))); + } + function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { - // If any property contains context sensitive functions that have been skipped, the source type - // is incomplete and we can't infer a meaningful input type. - if (getObjectFlags(source) & ObjectFlags.NonInferrableType || getPropertiesOfType(source).length === 0 && !getIndexInfoOfType(source, IndexKind.String)) { + // We consider a source type reverse mappable if it has a string index signature or if + // it has one or more properties and all properties have inferable types. + const properties = getPropertiesOfType(source); + const isReverseMappable = getIndexInfoOfType(source, IndexKind.String) || + properties.length !== 0 && every(properties, prop => isPartiallyInferableType(getTypeOfSymbol(prop))); + if (!isReverseMappable) { return undefined; } // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been