Fixed an issue with contextual type for intersection properties (take 2) (#52095)

This commit is contained in:
Mateusz Burzyński
2024-08-19 23:14:11 +02:00
committed by GitHub
parent 6894ff7f38
commit e6edc567a3
44 changed files with 4767 additions and 18 deletions

View File

@@ -31672,33 +31672,106 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).links.type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0);
}
function isExcludedMappedPropertyName(constraint: Type, propertyNameType: Type): boolean {
if (constraint.flags & TypeFlags.Conditional) {
const type = constraint as ConditionalType;
return !!(getReducedType(getTrueTypeFromConditionalType(type)).flags & TypeFlags.Never) &&
getActualTypeVariable(getFalseTypeFromConditionalType(type)) === getActualTypeVariable(type.checkType) &&
isTypeAssignableTo(propertyNameType, type.extendsType);
}
if (constraint.flags & TypeFlags.Intersection) {
return some((constraint as IntersectionType).types, t => isExcludedMappedPropertyName(t, propertyNameType));
}
return false;
}
function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) {
return mapType(type, t => {
if (isGenericMappedType(t) && getMappedTypeNameTypeKind(t) !== MappedTypeNameTypeKind.Remapping) {
const constraint = getConstraintTypeFromMappedType(t);
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
return substituteIndexedMappedType(t, propertyNameType);
if (t.flags & TypeFlags.Intersection) {
let types: Type[] | undefined;
let indexInfoCandidates: Type[] | undefined;
let ignoreIndexInfos = false;
for (const constituentType of (t as IntersectionType).types) {
if (!(constituentType.flags & TypeFlags.Object)) {
continue;
}
if (isGenericMappedType(constituentType) && getMappedTypeNameTypeKind(constituentType) !== MappedTypeNameTypeKind.Remapping) {
const substitutedType = getIndexedMappedTypeSubstitutedTypeOfContextualType(constituentType, name, nameType);
types = appendContextualPropertyTypeConstituent(types, substitutedType);
continue;
}
const propertyType = getTypeOfConcretePropertyOfContextualType(constituentType, name);
if (!propertyType) {
if (!ignoreIndexInfos) {
indexInfoCandidates = append(indexInfoCandidates, constituentType);
}
continue;
}
ignoreIndexInfos = true;
indexInfoCandidates = undefined;
types = appendContextualPropertyTypeConstituent(types, propertyType);
}
}
else if (t.flags & TypeFlags.StructuredType) {
const prop = getPropertyOfType(t, name);
if (prop) {
return isCircularMappedProperty(prop) ? undefined : removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional));
}
if (isTupleType(t) && isNumericLiteralName(name) && +name >= 0) {
const restType = getElementTypeOfSliceOfTupleType(t, t.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true);
if (restType) {
return restType;
if (indexInfoCandidates) {
for (const candidate of indexInfoCandidates) {
const indexInfoType = getTypeFromIndexInfosOfContextualType(candidate, name, nameType);
types = appendContextualPropertyTypeConstituent(types, indexInfoType);
}
}
return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
if (!types) {
return;
}
if (types.length === 1) {
return types[0];
}
return getIntersectionType(types);
}
return undefined;
if (!(t.flags & TypeFlags.Object)) {
return;
}
return isGenericMappedType(t) && getMappedTypeNameTypeKind(t) !== MappedTypeNameTypeKind.Remapping
? getIndexedMappedTypeSubstitutedTypeOfContextualType(t, name, nameType)
: getTypeOfConcretePropertyOfContextualType(t, name) ?? getTypeFromIndexInfosOfContextualType(t, name, nameType);
}, /*noReductions*/ true);
}
function appendContextualPropertyTypeConstituent(types: Type[] | undefined, type: Type | undefined) {
// any doesn't provide any contextual information but could spoil the overall result by nullifying contextual information provided by other intersection constituents
// so it gets replaced with `unknown` as `T & unknown` is just `T` and all types computed based on the contextual information provided by other constituens are still assignable to any
return type ? append(types, type.flags & TypeFlags.Any ? unknownType : type) : types;
}
function getIndexedMappedTypeSubstitutedTypeOfContextualType(type: MappedType, name: __String, nameType: Type | undefined) {
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
const constraint = getConstraintTypeFromMappedType(type);
// special case for conditional types pretending to be negated types
if (type.nameType && isExcludedMappedPropertyName(type.nameType, propertyNameType) || isExcludedMappedPropertyName(constraint, propertyNameType)) {
return;
}
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
if (!isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
return;
}
return substituteIndexedMappedType(type, propertyNameType);
}
function getTypeOfConcretePropertyOfContextualType(type: Type, name: __String) {
const prop = getPropertyOfType(type, name);
if (!prop || isCircularMappedProperty(prop)) {
return;
}
return removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional));
}
function getTypeFromIndexInfosOfContextualType(type: Type, name: __String, nameType: Type | undefined) {
if (isTupleType(type) && isNumericLiteralName(name) && +name >= 0) {
const restType = getElementTypeOfSliceOfTupleType(type, type.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true);
if (restType) {
return restType;
}
}
return findApplicableIndexInfo(getIndexInfosOfStructuredType(type), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
}
// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
// the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one
// exists. Otherwise, it is the type of the string index signature in T, if one exists.