Allow type comparison when target is generic mapped type with key remapping (#45700)

* Add case for type related to for generic mapped type with as clause target

* Clean up code and add baselines

* cleanup
This commit is contained in:
Gabriela Araujo Britto
2021-09-08 10:58:52 -07:00
committed by GitHub
parent 617251f2e0
commit 4f5bbd00e6
6 changed files with 350 additions and 15 deletions

View File

@@ -18625,39 +18625,60 @@ namespace ts {
originalErrorInfo = undefined;
}
}
else if (isGenericMappedType(target) && !target.declaration.nameType) {
// A source type T is related to a target type { [P in X]: T[P] }
const template = getTemplateTypeFromMappedType(target);
else if (isGenericMappedType(target)) {
// Check if source type `S` is related to target type `{ [P in Q]: T }` or `{ [P in Q as R]: T}`.
const keysRemapped = !!target.declaration.nameType;
const templateType = getTemplateTypeFromMappedType(target);
const modifiers = getMappedTypeModifiers(target);
if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) {
if (template.flags & TypeFlags.IndexedAccess && (template as IndexedAccessType).objectType === source &&
(template as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) {
// If the mapped type has shape `{ [P in Q]: T[P] }`,
// source `S` is related to target if `T` = `S`, i.e. `S` is related to `{ [P in Q]: S[P] }`.
if (!keysRemapped && templateType.flags & TypeFlags.IndexedAccess && (templateType as IndexedAccessType).objectType === source &&
(templateType as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) {
return Ternary.True;
}
if (!isGenericMappedType(source)) {
const targetConstraint = getConstraintTypeFromMappedType(target);
// If target has shape `{ [P in Q as R]: T}`, then its keys have type `R`.
// If target has shape `{ [P in Q]: T }`, then its keys have type `Q`.
const targetKeys = keysRemapped ? getNameTypeFromMappedType(target)! : getConstraintTypeFromMappedType(target);
// Type of the keys of source type `S`, i.e. `keyof S`.
const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true);
const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional;
const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined;
// A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X.
// A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X.
const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, sourceKeys) : undefined;
// A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`.
// A source type `S` is related to a target type `{ [P in Q as R]: T }` if `R` is related to `keyof S` and `S[R]` is related to `T.
// A source type `S` is related to a target type `{ [P in Q]?: T }` if some constituent `Q'` of `Q` is related to `keyof S` and `S[Q']` is related to `T`.
// A source type `S` is related to a target type `{ [P in Q as R]?: T }` if some constituent `R'` of `R` is related to `keyof S` and `S[R']` is related to `T`.
if (includeOptional
? !(filteredByApplicability!.flags & TypeFlags.Never)
: isRelatedTo(targetConstraint, sourceKeys)) {
const templateType = getTemplateTypeFromMappedType(target);
: isRelatedTo(targetKeys, sourceKeys)) {
const typeParameter = getTypeParameterFromMappedType(target);
// Fastpath: When the template has the form Obj[P] where P is the mapped type parameter, directly compare `source` with `Obj`
// to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `source[P]`
// Fastpath: When the template type has the form `Obj[P]` where `P` is the mapped type parameter, directly compare source `S` with `Obj`
// to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `S[P]`.
const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable);
if (nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) {
if (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) {
if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, reportErrors)) {
return result;
}
}
else {
const indexingType = filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter;
// We need to compare the type of a property on the source type `S` to the type of the same property on the target type,
// so we need to construct an indexing type representing a property, and then use indexing type to index the source type for comparison.
// If the target type has shape `{ [P in Q]: T }`, then a property of the target has type `P`.
// If the target type has shape `{ [P in Q]?: T }`, then a property of the target has type `P`,
// but the property is optional, so we only want to compare properties `P` that are common between `keyof S` and `Q`.
// If the target type has shape `{ [P in Q as R]: T }`, then a property of the target has type `R`.
// If the target type has shape `{ [P in Q as R]?: T }`, then a property of the target has type `R`,
// but the property is optional, so we only want to compare properties `R` that are common between `keyof S` and `R`.
const indexingType = keysRemapped
? (filteredByApplicability || targetKeys)
: filteredByApplicability
? getIntersectionType([filteredByApplicability, typeParameter])
: typeParameter;
const indexedAccessType = getIndexedAccessType(source, indexingType);
// Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type.
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
return result;
}