Add missing mapped type indexed access constraint (#47370)

* Type { [P in K]: E }[X] has constraint E with X substutited for P

* Add regression test

* Fix PragmaMap and ReadonlyPragmaMap declarations

* Explore additional constraint

* Revert previous change

* Add tests
This commit is contained in:
Anders Hejlsberg
2022-01-11 07:23:18 -08:00
committed by GitHub
parent 852b1c2b73
commit 4d6dd119d5
5 changed files with 273 additions and 7 deletions

View File

@@ -11749,6 +11749,11 @@ namespace ts {
}
function getConstraintFromIndexedAccess(type: IndexedAccessType) {
if (isMappedTypeGenericIndexedAccess(type)) {
// For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic,
// we substitute an instantiation of E where P is replaced with X.
return substituteIndexedMappedType(type.objectType as MappedType, type.indexType);
}
const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType);
if (indexConstraint && indexConstraint !== type.indexType) {
const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags);
@@ -11962,6 +11967,11 @@ namespace ts {
return constraint ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType;
}
if (t.flags & TypeFlags.IndexedAccess) {
if (isMappedTypeGenericIndexedAccess(t)) {
// For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic,
// we substitute an instantiation of E where P is replaced with X.
return getBaseConstraint(substituteIndexedMappedType((t as IndexedAccessType).objectType as MappedType, (t as IndexedAccessType).indexType));
}
const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType);
const baseIndexType = getBaseConstraint((t as IndexedAccessType).indexType);
const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).accessFlags);
@@ -12055,13 +12065,7 @@ namespace ts {
* type itself.
*/
function getApparentType(type: Type): Type {
// We obtain the base constraint for all instantiable types, except indexed access types of the form
// { [P in K]: E }[X], where K is non-generic and X is generic. For those types, we instead substitute an
// instantiation of E where P is replaced with X. We do this because getBaseConstraintOfType directly
// lowers to an instantiation where X's constraint is substituted for X, which isn't always desirable.
const t = !(type.flags & TypeFlags.Instantiable) ? type :
isMappedTypeGenericIndexedAccess(type) ? substituteIndexedMappedType((type as IndexedAccessType).objectType as MappedType, (type as IndexedAccessType).indexType) :
getBaseConstraintOfType(type) || unknownType;
const t = !(type.flags & TypeFlags.Instantiable) ? type : getBaseConstraintOfType(type) || unknownType;
return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) :
t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType) :
t.flags & TypeFlags.StringLike ? globalStringType :
@@ -19220,6 +19224,17 @@ namespace ts {
resetErrorInfo(saveErrorInfo);
return result;
}
if (isMappedTypeGenericIndexedAccess(source)) {
// For an indexed access type { [P in K]: E}[X], above we have already explored an instantiation of E with X
// substituted for P. We also want to explore type { [P in K]: E }[C], where C is the constraint of X.
const indexConstraint = getConstraintOfType((source as IndexedAccessType).indexType);
if (indexConstraint) {
if (result = isRelatedTo(getIndexedAccessType((source as IndexedAccessType).objectType, indexConstraint), target, RecursionFlags.Source, reportErrors)) {
resetErrorInfo(saveErrorInfo);
return result;
}
}
}
}
}
else if (source.flags & TypeFlags.Index) {