mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-22 12:03:44 -05:00
Merge pull request #30769 from Microsoft/saferIndexedAccessTypes
Improve soundness of indexed access types
This commit is contained in:
@@ -708,6 +708,13 @@ namespace ts {
|
||||
Signature = 1 << 0, // Obtaining contextual signature
|
||||
}
|
||||
|
||||
const enum AccessFlags {
|
||||
None = 0,
|
||||
NoIndexSignatures = 1 << 0,
|
||||
Writing = 1 << 1,
|
||||
CacheSymbol = 1 << 2,
|
||||
}
|
||||
|
||||
const enum CallbackCheck {
|
||||
None,
|
||||
Bivariant,
|
||||
@@ -7231,7 +7238,15 @@ namespace ts {
|
||||
return getIndexType(getApparentType((<IndexType>type).type));
|
||||
}
|
||||
if (type.flags & TypeFlags.Conditional) {
|
||||
return getLowerBoundOfConditionalType(<ConditionalType>type);
|
||||
if ((<ConditionalType>type).root.isDistributive) {
|
||||
const checkType = (<ConditionalType>type).checkType;
|
||||
const constraint = getLowerBoundOfKeyType(checkType);
|
||||
if (constraint !== checkType) {
|
||||
const mapper = makeUnaryTypeMapper((<ConditionalType>type).root.checkType, constraint);
|
||||
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers(mapper, (<ConditionalType>type).mapper));
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
if (type.flags & TypeFlags.Union) {
|
||||
return getUnionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
|
||||
@@ -7242,17 +7257,6 @@ namespace ts {
|
||||
return neverType;
|
||||
}
|
||||
|
||||
function getLowerBoundOfConditionalType(type: ConditionalType) {
|
||||
if (type.root.isDistributive) {
|
||||
const constraint = getLowerBoundOfKeyType(type.checkType);
|
||||
if (constraint !== type.checkType) {
|
||||
const mapper = makeUnaryTypeMapper(type.root.checkType, constraint);
|
||||
return getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper));
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/** Resolve the members of a mapped type { [P in K]: T } */
|
||||
function resolveMappedTypeMembers(type: MappedType) {
|
||||
const members: SymbolTable = createSymbolTable();
|
||||
@@ -7521,15 +7525,18 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getConstraintFromIndexedAccess(type: IndexedAccessType) {
|
||||
const objectType = getConstraintOfType(type.objectType) || type.objectType;
|
||||
if (objectType !== type.objectType) {
|
||||
const constraint = getIndexedAccessType(objectType, type.indexType, /*accessNode*/ undefined, errorType);
|
||||
if (constraint && constraint !== errorType) {
|
||||
return constraint;
|
||||
const indexConstraint = getConstraintOfType(type.indexType);
|
||||
if (indexConstraint && indexConstraint !== type.indexType) {
|
||||
const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint);
|
||||
if (indexedAccess) {
|
||||
return indexedAccess;
|
||||
}
|
||||
}
|
||||
const baseConstraint = getBaseConstraintOfType(type);
|
||||
return baseConstraint && baseConstraint !== type ? baseConstraint : undefined;
|
||||
const objectConstraint = getConstraintOfType(type.objectType);
|
||||
if (objectConstraint && objectConstraint !== type.objectType) {
|
||||
return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getDefaultConstraintOfConditionalType(type: ConditionalType) {
|
||||
@@ -7558,7 +7565,7 @@ namespace ts {
|
||||
// a union - once negated types exist and are applied to the conditional false branch, this "constraint"
|
||||
// likely doesn't need to exist.
|
||||
if (type.root.isDistributive && type.restrictiveInstantiation !== type) {
|
||||
const simplified = getSimplifiedType(type.checkType);
|
||||
const simplified = getSimplifiedType(type.checkType, /*writing*/ false);
|
||||
const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified;
|
||||
if (constraint && constraint !== type.checkType) {
|
||||
const mapper = makeUnaryTypeMapper(type.root.checkType, constraint);
|
||||
@@ -7665,7 +7672,7 @@ namespace ts {
|
||||
return t.immediateBaseConstraint = noConstraintType;
|
||||
}
|
||||
constraintDepth++;
|
||||
let result = computeBaseConstraint(getSimplifiedType(t));
|
||||
let result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false));
|
||||
constraintDepth--;
|
||||
if (!popTypeResolution()) {
|
||||
if (t.flags & TypeFlags.TypeParameter) {
|
||||
@@ -7718,8 +7725,8 @@ namespace ts {
|
||||
if (t.flags & TypeFlags.IndexedAccess) {
|
||||
const baseObjectType = getBaseConstraint((<IndexedAccessType>t).objectType);
|
||||
const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
|
||||
const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType, /*accessNode*/ undefined, errorType) : undefined;
|
||||
return baseIndexedAccess && baseIndexedAccess !== errorType ? getBaseConstraint(baseIndexedAccess) : undefined;
|
||||
const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType);
|
||||
return baseIndexedAccess && getBaseConstraint(baseIndexedAccess);
|
||||
}
|
||||
if (t.flags & TypeFlags.Conditional) {
|
||||
const constraint = getConstraintFromConditionalType(<ConditionalType>t);
|
||||
@@ -9833,16 +9840,16 @@ namespace ts {
|
||||
return numberIndexInfo !== enumNumberIndexInfo ? numberIndexInfo : undefined;
|
||||
}
|
||||
|
||||
function getIndexType(type: Type, stringsOnly = keyofStringsOnly): Type {
|
||||
return type.flags & TypeFlags.Union ? getIntersectionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly))) :
|
||||
type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly))) :
|
||||
function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type {
|
||||
return type.flags & TypeFlags.Union ? getIntersectionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
|
||||
type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
|
||||
maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive) ? getIndexTypeForGenericType(<InstantiableType | UnionOrIntersectionType>type, stringsOnly) :
|
||||
getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(<MappedType>type) :
|
||||
getObjectFlags(type) & ObjectFlags.Mapped ? filterType(getConstraintTypeFromMappedType(<MappedType>type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number))) :
|
||||
type === wildcardType ? wildcardType :
|
||||
type.flags & TypeFlags.Any ? keyofConstraintType :
|
||||
stringsOnly ? getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
|
||||
getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
|
||||
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
|
||||
stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
|
||||
!noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
|
||||
!noIndexSignatures && getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
|
||||
getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique);
|
||||
}
|
||||
|
||||
@@ -9914,7 +9921,7 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, cacheSymbol: boolean, missingType: Type) {
|
||||
function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
|
||||
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
|
||||
const propName = isTypeUsableAsPropertyName(indexType) ?
|
||||
getPropertyNameFromType(indexType) :
|
||||
@@ -9931,13 +9938,12 @@ namespace ts {
|
||||
markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword);
|
||||
if (isAssignmentTarget(accessExpression) && (isReferenceToReadonlyEntity(accessExpression, prop) || isReferenceThroughNamespaceImport(accessExpression))) {
|
||||
error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop));
|
||||
return missingType;
|
||||
return undefined;
|
||||
}
|
||||
if (cacheSymbol) {
|
||||
if (accessFlags & AccessFlags.CacheSymbol) {
|
||||
getNodeLinks(accessNode!).resolvedSymbol = prop;
|
||||
}
|
||||
}
|
||||
|
||||
const propType = getTypeOfSymbol(prop);
|
||||
return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ?
|
||||
getFlowTypeOfReference(accessExpression, propType) :
|
||||
@@ -9962,15 +9968,25 @@ namespace ts {
|
||||
return objectType;
|
||||
}
|
||||
const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) ||
|
||||
getIndexInfoOfType(objectType, IndexKind.String) ||
|
||||
undefined;
|
||||
getIndexInfoOfType(objectType, IndexKind.String);
|
||||
if (indexInfo) {
|
||||
if (accessFlags & AccessFlags.NoIndexSignatures) {
|
||||
if (accessExpression) {
|
||||
error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) {
|
||||
const indexNode = getIndexNodeForAccessExpression(accessNode);
|
||||
error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType));
|
||||
return indexInfo.type;
|
||||
}
|
||||
else if (accessExpression && indexInfo.isReadonly && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) {
|
||||
error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));
|
||||
if (indexInfo.isReadonly && (accessFlags & AccessFlags.Writing || accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression)))) {
|
||||
if (accessExpression) {
|
||||
error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));
|
||||
return indexInfo.type;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return indexInfo.type;
|
||||
}
|
||||
@@ -10003,7 +10019,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
}
|
||||
return missingType;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (isJSLiteralType(objectType)) {
|
||||
@@ -10024,7 +10040,7 @@ namespace ts {
|
||||
if (isTypeAny(indexType)) {
|
||||
return indexType;
|
||||
}
|
||||
return missingType;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) {
|
||||
@@ -10045,53 +10061,68 @@ namespace ts {
|
||||
return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.Index);
|
||||
}
|
||||
|
||||
function getSimplifiedType(type: Type): Type {
|
||||
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type) : type;
|
||||
function getSimplifiedType(type: Type, writing: boolean): Type {
|
||||
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type, writing) : type;
|
||||
}
|
||||
|
||||
function distributeIndexOverObjectType(objectType: Type, indexType: Type) {
|
||||
// (T | U)[K] -> T[K] | U[K]
|
||||
if (objectType.flags & TypeFlags.Union) {
|
||||
return mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType)));
|
||||
}
|
||||
function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) {
|
||||
// (T | U)[K] -> T[K] | U[K] (reading)
|
||||
// (T | U)[K] -> T[K] & U[K] (writing)
|
||||
// (T & U)[K] -> T[K] & U[K]
|
||||
if (objectType.flags & TypeFlags.Intersection) {
|
||||
return getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType))));
|
||||
if (objectType.flags & TypeFlags.UnionOrIntersection) {
|
||||
const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing));
|
||||
return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types);
|
||||
}
|
||||
}
|
||||
|
||||
function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) {
|
||||
// T[A | B] -> T[A] | T[B] (reading)
|
||||
// T[A | B] -> T[A] & T[B] (writing)
|
||||
if (indexType.flags & TypeFlags.Union) {
|
||||
const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing));
|
||||
return writing ? getIntersectionType(types) : getUnionType(types);
|
||||
}
|
||||
}
|
||||
|
||||
// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
|
||||
// the type itself if no transformation is possible.
|
||||
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
|
||||
if (type.simplified) {
|
||||
return type.simplified === circularConstraintType ? type : type.simplified;
|
||||
// the type itself if no transformation is possible. The writing flag indicates that the type is
|
||||
// the target of an assignment.
|
||||
function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): Type {
|
||||
const cache = writing ? "simplifiedForWriting" : "simplifiedForReading";
|
||||
if (type[cache]) {
|
||||
return type[cache] === circularConstraintType ? type : type[cache]!;
|
||||
}
|
||||
type.simplified = circularConstraintType;
|
||||
type[cache] = circularConstraintType;
|
||||
// We recursively simplify the object type as it may in turn be an indexed access type. For example, with
|
||||
// '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type.
|
||||
const objectType = getSimplifiedType(type.objectType);
|
||||
const indexType = getSimplifiedType(type.indexType);
|
||||
// T[A | B] -> T[A] | T[B]
|
||||
if (indexType.flags & TypeFlags.Union) {
|
||||
return type.simplified = mapType(indexType, t => getSimplifiedType(getIndexedAccessType(objectType, t)));
|
||||
const objectType = getSimplifiedType(type.objectType, writing);
|
||||
const indexType = getSimplifiedType(type.indexType, writing);
|
||||
// T[A | B] -> T[A] | T[B] (reading)
|
||||
// T[A | B] -> T[A] & T[B] (writing)
|
||||
const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing);
|
||||
if (distributedOverIndex) {
|
||||
return type[cache] = distributedOverIndex;
|
||||
}
|
||||
// Only do the inner distributions if the index can no longer be instantiated to cause index distribution again
|
||||
if (!(indexType.flags & TypeFlags.Instantiable)) {
|
||||
const simplified = distributeIndexOverObjectType(objectType, indexType);
|
||||
if (simplified) {
|
||||
return type.simplified = simplified;
|
||||
// (T | U)[K] -> T[K] | U[K] (reading)
|
||||
// (T | U)[K] -> T[K] & U[K] (writing)
|
||||
// (T & U)[K] -> T[K] & U[K]
|
||||
const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing);
|
||||
if (distributedOverObject) {
|
||||
return type[cache] = distributedOverObject;
|
||||
}
|
||||
}
|
||||
// So ultimately:
|
||||
// So ultimately (reading):
|
||||
// ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2]
|
||||
|
||||
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
|
||||
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
|
||||
// construct the type Box<T[X]>.
|
||||
if (isGenericMappedType(objectType)) {
|
||||
return type.simplified = mapType(substituteIndexedMappedType(objectType, type.indexType), getSimplifiedType);
|
||||
return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing));
|
||||
}
|
||||
return type.simplified = type;
|
||||
return type[cache] = type;
|
||||
}
|
||||
|
||||
function substituteIndexedMappedType(objectType: MappedType, index: Type) {
|
||||
@@ -10100,10 +10131,19 @@ namespace ts {
|
||||
return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
|
||||
}
|
||||
|
||||
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, missingType = accessNode ? errorType : unknownType): Type {
|
||||
function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression): Type {
|
||||
return getIndexedAccessTypeOrUndefined(objectType, indexType, accessNode, AccessFlags.None) || (accessNode ? errorType : unknownType);
|
||||
}
|
||||
|
||||
function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None): Type | undefined {
|
||||
if (objectType === wildcardType || indexType === wildcardType) {
|
||||
return wildcardType;
|
||||
}
|
||||
// If the object type has a string index signature and no other members we know that the result will
|
||||
// always be the type of that index signature and we can simplify accordingly.
|
||||
if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) {
|
||||
indexType = stringType;
|
||||
}
|
||||
// If the index type is generic, or if the object type is generic and doesn't originate in an expression,
|
||||
// we are performing a higher-order index access where we cannot meaningfully access the properties of the
|
||||
// object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in
|
||||
@@ -10129,25 +10169,25 @@ namespace ts {
|
||||
const propTypes: Type[] = [];
|
||||
let wasMissingProp = false;
|
||||
for (const t of (<UnionType>indexType).types) {
|
||||
const propType = getPropertyTypeForIndexType(apparentObjectType, t, accessNode, /*cacheSymbol*/ false, missingType);
|
||||
if (propType === missingType) {
|
||||
if (!accessNode) {
|
||||
// If there's no error node, we can immeditely stop, since error reporting is off
|
||||
return missingType;
|
||||
}
|
||||
else {
|
||||
// Otherwise we set a flag and return at the end of the loop so we still mark all errors
|
||||
wasMissingProp = true;
|
||||
}
|
||||
const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, accessNode, accessFlags);
|
||||
if (propType) {
|
||||
propTypes.push(propType);
|
||||
}
|
||||
else if (!accessNode) {
|
||||
// If there's no error node, we can immeditely stop, since error reporting is off
|
||||
return undefined;
|
||||
}
|
||||
else {
|
||||
// Otherwise we set a flag and return at the end of the loop so we still mark all errors
|
||||
wasMissingProp = true;
|
||||
}
|
||||
propTypes.push(propType);
|
||||
}
|
||||
if (wasMissingProp) {
|
||||
return missingType;
|
||||
return undefined;
|
||||
}
|
||||
return getUnionType(propTypes);
|
||||
return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes) : getUnionType(propTypes);
|
||||
}
|
||||
return getPropertyTypeForIndexType(apparentObjectType, indexType, accessNode, /*cacheSymbol*/ true, missingType);
|
||||
return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol);
|
||||
}
|
||||
|
||||
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
|
||||
@@ -11536,10 +11576,10 @@ namespace ts {
|
||||
let reportedError = false;
|
||||
for (let status = iterator.next(); !status.done; status = iterator.next()) {
|
||||
const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value;
|
||||
const targetPropType = getIndexedAccessType(target, nameType, /*accessNode*/ undefined, errorType);
|
||||
if (targetPropType === errorType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables
|
||||
const sourcePropType = getIndexedAccessType(source, nameType, /*accessNode*/ undefined, errorType);
|
||||
if (sourcePropType !== errorType && targetPropType !== errorType && !checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) {
|
||||
const targetPropType = getIndexedAccessTypeOrUndefined(target, nameType);
|
||||
if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables
|
||||
const sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType);
|
||||
if (sourcePropType && !checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) {
|
||||
const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined);
|
||||
if (elaborated) {
|
||||
reportedError = true;
|
||||
@@ -11981,6 +12021,12 @@ namespace ts {
|
||||
return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && isEmptyObjectType(type);
|
||||
}
|
||||
|
||||
function isStringIndexSignatureOnlyType(type: Type): boolean {
|
||||
return type.flags & TypeFlags.Object && getPropertiesOfType(type).length === 0 && getIndexInfoOfType(type, IndexKind.String) && !getIndexInfoOfType(type, IndexKind.Number) ||
|
||||
type.flags & TypeFlags.UnionOrIntersection && every((<UnionOrIntersectionType>type).types, isStringIndexSignatureOnlyType) ||
|
||||
false;
|
||||
}
|
||||
|
||||
function isEnumTypeRelatedTo(sourceSymbol: Symbol, targetSymbol: Symbol, errorReporter?: ErrorReporter) {
|
||||
if (sourceSymbol === targetSymbol) {
|
||||
return true;
|
||||
@@ -12239,10 +12285,10 @@ namespace ts {
|
||||
target = (<SubstitutionType>target).typeVariable;
|
||||
}
|
||||
if (source.flags & TypeFlags.IndexedAccess) {
|
||||
source = getSimplifiedType(source);
|
||||
source = getSimplifiedType(source, /*writing*/ false);
|
||||
}
|
||||
if (target.flags & TypeFlags.IndexedAccess) {
|
||||
target = getSimplifiedType(target);
|
||||
target = getSimplifiedType(target, /*writing*/ true);
|
||||
}
|
||||
|
||||
// Try to see if we're relating something like `Foo` -> `Bar | null | undefined`.
|
||||
@@ -12822,7 +12868,7 @@ namespace ts {
|
||||
}
|
||||
// A type S is assignable to keyof T if S is assignable to keyof C, where C is the
|
||||
// simplified form of T or, if T doesn't simplify, the constraint of T.
|
||||
const simplified = getSimplifiedType((<IndexType>target).type);
|
||||
const simplified = getSimplifiedType((<IndexType>target).type, /*writing*/ false);
|
||||
const constraint = simplified !== (<IndexType>target).type ? simplified : getConstraintOfType((<IndexType>target).type);
|
||||
if (constraint) {
|
||||
// We require Ternary.True here such that circular constraints don't cause
|
||||
@@ -12835,13 +12881,17 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else if (target.flags & TypeFlags.IndexedAccess) {
|
||||
// A type S is related to a type T[K], where T and K aren't both type variables, if S is related to C,
|
||||
// where C is the base constraint of T[K]
|
||||
if (relation !== identityRelation &&
|
||||
!(isGenericObjectType((<IndexedAccessType>target).objectType) && isGenericIndexType((<IndexedAccessType>target).indexType))) {
|
||||
const constraint = getBaseConstraintOfType(target);
|
||||
if (constraint && constraint !== target) {
|
||||
if (result = isRelatedTo(source, constraint, reportErrors)) {
|
||||
// A type S is related to a type T[K] if S is related to C, where C is the base
|
||||
// constraint of T[K] for writing.
|
||||
if (relation !== identityRelation) {
|
||||
const objectType = (<IndexedAccessType>target).objectType;
|
||||
const indexType = (<IndexedAccessType>target).indexType;
|
||||
const baseObjectType = getBaseConstraintOfType(objectType) || objectType;
|
||||
const baseIndexType = getBaseConstraintOfType(indexType) || indexType;
|
||||
if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) {
|
||||
const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0);
|
||||
const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, /*accessNode*/ undefined, accessFlags);
|
||||
if (constraint && (result = isRelatedTo(source, constraint, reportErrors))) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -12858,7 +12908,7 @@ namespace ts {
|
||||
}
|
||||
if (!isGenericMappedType(source)) {
|
||||
const targetConstraint = getConstraintTypeFromMappedType(target);
|
||||
const sourceKeys = getIndexType(source);
|
||||
const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true);
|
||||
const hasOptionalUnionKeys = modifiers & MappedTypeModifiers.IncludeOptional && targetConstraint.flags & TypeFlags.Union;
|
||||
const filteredByApplicability = hasOptionalUnionKeys ? filterType(targetConstraint, t => !!isRelatedTo(t, 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.
|
||||
@@ -12890,23 +12940,25 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
const constraint = getConstraintOfType(<TypeVariable>source);
|
||||
if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
|
||||
// A type variable with no constraint is not related to the non-primitive object type.
|
||||
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
|
||||
else {
|
||||
const constraint = getConstraintOfType(<TypeVariable>source);
|
||||
if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
|
||||
// A type variable with no constraint is not related to the non-primitive object type.
|
||||
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
|
||||
errorInfo = saveErrorInfo;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
|
||||
else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) {
|
||||
errorInfo = saveErrorInfo;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
|
||||
else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) {
|
||||
// slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example
|
||||
else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent)) {
|
||||
errorInfo = saveErrorInfo;
|
||||
return result;
|
||||
}
|
||||
// slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example
|
||||
else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent)) {
|
||||
errorInfo = saveErrorInfo;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (source.flags & TypeFlags.Index) {
|
||||
@@ -14696,16 +14748,16 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
// Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
|
||||
const simplified = getSimplifiedType(target);
|
||||
const simplified = getSimplifiedType(target, /*writing*/ false);
|
||||
if (simplified !== target) {
|
||||
inferFromTypesOnce(source, simplified);
|
||||
}
|
||||
else if (target.flags & TypeFlags.IndexedAccess) {
|
||||
const indexType = getSimplifiedType((target as IndexedAccessType).indexType);
|
||||
const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false);
|
||||
// Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider
|
||||
// that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can.
|
||||
if (indexType.flags & TypeFlags.Instantiable) {
|
||||
const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType), indexType);
|
||||
const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false);
|
||||
if (simplified && simplified !== target) {
|
||||
inferFromTypesOnce(source, simplified);
|
||||
}
|
||||
@@ -15741,24 +15793,19 @@ namespace ts {
|
||||
if (!(type.flags & TypeFlags.Union)) {
|
||||
return mapper(type);
|
||||
}
|
||||
const types = (<UnionType>type).types;
|
||||
let mappedType: Type | undefined;
|
||||
let mappedTypes: Type[] | undefined;
|
||||
for (const current of types) {
|
||||
const t = mapper(current);
|
||||
if (t) {
|
||||
if (!mappedType) {
|
||||
mappedType = t;
|
||||
}
|
||||
else if (!mappedTypes) {
|
||||
mappedTypes = [mappedType, t];
|
||||
for (const t of (<UnionType>type).types) {
|
||||
const mapped = mapper(t);
|
||||
if (mapped) {
|
||||
if (!mappedTypes) {
|
||||
mappedTypes = [mapped];
|
||||
}
|
||||
else {
|
||||
mappedTypes.push(t);
|
||||
mappedTypes.push(mapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
return mappedTypes ? getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : mappedType;
|
||||
return mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal);
|
||||
}
|
||||
|
||||
function extractTypesOfKind(type: Type, kind: TypeFlags) {
|
||||
@@ -19595,7 +19642,7 @@ namespace ts {
|
||||
markAliasReferenced(parentSymbol, node);
|
||||
}
|
||||
if (!prop) {
|
||||
const indexInfo = getIndexInfoOfType(apparentType, IndexKind.String);
|
||||
const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined;
|
||||
if (!(indexInfo && indexInfo.type)) {
|
||||
if (isJSLiteralType(leftType)) {
|
||||
return anyType;
|
||||
@@ -20000,7 +20047,12 @@ namespace ts {
|
||||
return errorType;
|
||||
}
|
||||
|
||||
return checkIndexedAccessIndexType(getIndexedAccessType(objectType, isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType, node), node);
|
||||
const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType;
|
||||
const accessFlags = isAssignmentTarget(node) ?
|
||||
AccessFlags.Writing | (isGenericObjectType(objectType) ? AccessFlags.NoIndexSignatures : 0) :
|
||||
AccessFlags.None;
|
||||
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType;
|
||||
return checkIndexedAccessIndexType(indexedAccessType, node);
|
||||
}
|
||||
|
||||
function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
|
||||
@@ -24595,7 +24647,7 @@ namespace ts {
|
||||
return type;
|
||||
}
|
||||
error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType));
|
||||
return type;
|
||||
return errorType;
|
||||
}
|
||||
|
||||
function checkIndexedAccessType(node: IndexedAccessTypeNode) {
|
||||
|
||||
@@ -7797,7 +7797,7 @@ namespace ts {
|
||||
const referencedFiles = context.referencedFiles;
|
||||
const typeReferenceDirectives = context.typeReferenceDirectives;
|
||||
const libReferenceDirectives = context.libReferenceDirectives;
|
||||
forEach(toArray(entryOrList), (arg: PragmaPseudoMap["reference"]) => {
|
||||
forEach(toArray(entryOrList) as PragmaPseudoMap["reference"][], arg => {
|
||||
const { types, lib, path } = arg.arguments;
|
||||
if (arg.arguments["no-default-lib"]) {
|
||||
context.hasNoDefaultLib = true;
|
||||
@@ -7819,8 +7819,8 @@ namespace ts {
|
||||
}
|
||||
case "amd-dependency": {
|
||||
context.amdDependencies = map(
|
||||
toArray(entryOrList),
|
||||
(x: PragmaPseudoMap["amd-dependency"]) => ({ name: x.arguments.name, path: x.arguments.path }));
|
||||
toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][],
|
||||
x => ({ name: x.arguments.name, path: x.arguments.path }));
|
||||
break;
|
||||
}
|
||||
case "amd-module": {
|
||||
|
||||
@@ -4279,7 +4279,8 @@ namespace ts {
|
||||
objectType: Type;
|
||||
indexType: Type;
|
||||
constraint?: Type;
|
||||
simplified?: Type;
|
||||
simplifiedForReading?: Type;
|
||||
simplifiedForWriting?: Type;
|
||||
}
|
||||
|
||||
export type TypeVariable = TypeParameter | IndexedAccessType;
|
||||
|
||||
Reference in New Issue
Block a user