();
const includes = addTypesToIntersection(typeMembershipMap, 0 as TypeFlags, types);
const typeSet: Type[] = arrayFrom(typeMembershipMap.values());
let objectFlags = ObjectFlags.None;
// An intersection type is considered empty if it contains
// the type never, or
// more than one unit type or,
// an object type and a nullable type (null or undefined), or
// a string-like type and a type known to be non-string-like, or
// a number-like type and a type known to be non-number-like, or
// a symbol-like type and a type known to be non-symbol-like, or
// a void-like type and a type known to be non-void-like, or
// a non-primitive type and a type known to be primitive.
if (includes & TypeFlags.Never) {
return contains(typeSet, silentNeverType) ? silentNeverType : neverType;
}
if (
strictNullChecks && includes & TypeFlags.Nullable && includes & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.IncludesEmptyObject) ||
includes & TypeFlags.NonPrimitive && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) ||
includes & TypeFlags.StringLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) ||
includes & TypeFlags.NumberLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) ||
includes & TypeFlags.BigIntLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) ||
includes & TypeFlags.ESSymbolLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) ||
includes & TypeFlags.VoidLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)
) {
return neverType;
}
if (includes & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) {
return neverType;
}
if (includes & TypeFlags.Any) {
return includes & TypeFlags.IncludesWildcard ? wildcardType : includes & TypeFlags.IncludesError ? errorType : anyType;
}
if (!strictNullChecks && includes & TypeFlags.Nullable) {
return includes & TypeFlags.IncludesEmptyObject ? neverType : includes & TypeFlags.Undefined ? undefinedType : nullType;
}
if (
includes & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ||
includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral ||
includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral ||
includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol ||
includes & TypeFlags.Void && includes & TypeFlags.Undefined ||
includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.DefinitelyNonNullable
) {
if (!(flags & IntersectionFlags.NoSupertypeReduction)) removeRedundantSupertypes(typeSet, includes);
}
if (includes & TypeFlags.IncludesMissingType) {
typeSet[typeSet.indexOf(undefinedType)] = missingType;
}
if (typeSet.length === 0) {
return unknownType;
}
if (typeSet.length === 1) {
return typeSet[0];
}
if (typeSet.length === 2 && !(flags & IntersectionFlags.NoConstraintReduction)) {
const typeVarIndex = typeSet[0].flags & TypeFlags.TypeVariable ? 0 : 1;
const typeVariable = typeSet[typeVarIndex];
const primitiveType = typeSet[1 - typeVarIndex];
if (
typeVariable.flags & TypeFlags.TypeVariable &&
(primitiveType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) && !isGenericStringLikeType(primitiveType) || includes & TypeFlags.IncludesEmptyObject)
) {
// We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}.
const constraint = getBaseConstraintOfType(typeVariable);
// Check that T's constraint is similarly composed of primitive types, the object type, or {}.
if (constraint && everyType(constraint, t => !!(t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) || isEmptyAnonymousObjectType(t))) {
// If T's constraint is a subtype of P, simply return T. For example, given `T extends "a" | "b"`,
// the intersection `T & string` reduces to just T.
if (isTypeStrictSubtypeOf(constraint, primitiveType)) {
return typeVariable;
}
if (!(constraint.flags & TypeFlags.Union && someType(constraint, c => isTypeStrictSubtypeOf(c, primitiveType)))) {
// No constituent of T's constraint is a subtype of P. If P is also not a subtype of T's constraint,
// then the constraint and P are unrelated, and the intersection reduces to never. For example, given
// `T extends "a" | "b"`, the intersection `T & number` reduces to never.
if (!isTypeStrictSubtypeOf(primitiveType, constraint)) {
return neverType;
}
}
// Some constituent of T's constraint is a subtype of P, or P is a subtype of T's constraint. Thus,
// the intersection further constrains the type variable. For example, given `T extends string | number`,
// the intersection `T & "a"` is marked as a constrained type variable. Likewise, given `T extends "a" | 1`,
// the intersection `T & number` is marked as a constrained type variable.
objectFlags = ObjectFlags.IsConstrainedTypeVariable;
}
}
}
const id = getTypeListId(typeSet) + (flags & IntersectionFlags.NoConstraintReduction ? "*" : getAliasId(aliasSymbol, aliasTypeArguments));
let result = intersectionTypes.get(id);
if (!result) {
if (includes & TypeFlags.Union) {
if (intersectUnionsOfPrimitiveTypes(typeSet)) {
// When the intersection creates a reduced set (which might mean that *all* union types have
// disappeared), we restart the operation to get a new set of combined flags. Once we have
// reduced we'll never reduce again, so this occurs at most once.
result = getIntersectionType(typeSet, flags, aliasSymbol, aliasTypeArguments);
}
else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && (t as UnionType).types[0].flags & TypeFlags.Undefined))) {
const containedUndefinedType = some(typeSet, containsMissingType) ? missingType : undefinedType;
removeFromEach(typeSet, TypeFlags.Undefined);
result = getUnionType([getIntersectionType(typeSet, flags), containedUndefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
}
else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && ((t as UnionType).types[0].flags & TypeFlags.Null || (t as UnionType).types[1].flags & TypeFlags.Null)))) {
removeFromEach(typeSet, TypeFlags.Null);
result = getUnionType([getIntersectionType(typeSet, flags), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
}
else if (typeSet.length >= 3 && types.length > 2) {
// When we have three or more constituents, more than two inputs (to head off infinite reexpansion), some of which are unions, we employ a "divide and conquer" strategy
// where A & B & C & D is processed as (A & B) & (C & D). Since intersections of unions often produce far smaller
// unions of intersections than the full cartesian product (due to some intersections becoming `never`), this can
// dramatically reduce the overall work.
const middle = Math.floor(typeSet.length / 2);
result = getIntersectionType([getIntersectionType(typeSet.slice(0, middle), flags), getIntersectionType(typeSet.slice(middle), flags)], flags, aliasSymbol, aliasTypeArguments);
}
else {
// We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of
// the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type
// exceeds 100000 constituents, report an error.
if (!checkCrossProductUnion(typeSet)) {
return errorType;
}
const constituents = getCrossProductIntersections(typeSet, flags);
// We attach a denormalized origin type when at least one constituent of the cross-product union is an
// intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions) and
// the denormalized origin has fewer constituents than the union itself.
const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) && getConstituentCountOfTypes(constituents) > getConstituentCountOfTypes(typeSet) ? createOriginUnionOrIntersectionType(TypeFlags.Intersection, typeSet) : undefined;
result = getUnionType(constituents, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin);
}
}
else {
result = createIntersectionType(typeSet, objectFlags, aliasSymbol, aliasTypeArguments);
}
intersectionTypes.set(id, result);
}
return result;
}
function getCrossProductUnionSize(types: readonly Type[]) {
return reduceLeft(types, (n, t) => t.flags & TypeFlags.Union ? n * (t as UnionType).types.length : t.flags & TypeFlags.Never ? 0 : n, 1);
}
function checkCrossProductUnion(types: readonly Type[]) {
const size = getCrossProductUnionSize(types);
if (size >= 100000) {
tracing?.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_DepthLimit", { typeIds: types.map(t => t.id), size });
error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
return false;
}
return true;
}
function getCrossProductIntersections(types: readonly Type[], flags: IntersectionFlags) {
const count = getCrossProductUnionSize(types);
const intersections: Type[] = [];
for (let i = 0; i < count; i++) {
const constituents = types.slice();
let n = i;
for (let j = types.length - 1; j >= 0; j--) {
if (types[j].flags & TypeFlags.Union) {
const sourceTypes = (types[j] as UnionType).types;
const length = sourceTypes.length;
constituents[j] = sourceTypes[n % length];
n = Math.floor(n / length);
}
}
const t = getIntersectionType(constituents, flags);
if (!(t.flags & TypeFlags.Never)) intersections.push(t);
}
return intersections;
}
function getConstituentCount(type: Type): number {
return !(type.flags & TypeFlags.UnionOrIntersection) || type.aliasSymbol ? 1 :
type.flags & TypeFlags.Union && (type as UnionType).origin ? getConstituentCount((type as UnionType).origin!) :
getConstituentCountOfTypes((type as UnionOrIntersectionType).types);
}
function getConstituentCountOfTypes(types: Type[]): number {
return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0);
}
function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const aliasSymbol = getAliasSymbolForTypeNode(node);
const types = map(node.types, getTypeFromTypeNode);
// We perform no supertype reduction for X & {} or {} & X, where X is one of string, number, bigint,
// or a pattern literal template type. This enables union types like "a" | "b" | string & {} or
// "aa" | "ab" | `a${string}` which preserve the literal types for purposes of statement completion.
const emptyIndex = types.length === 2 ? types.indexOf(emptyTypeLiteralType) : -1;
const t = emptyIndex >= 0 ? types[1 - emptyIndex] : unknownType;
const noSupertypeReduction = !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt) || t.flags & TypeFlags.TemplateLiteral && isPatternLiteralType(t));
links.resolvedType = getIntersectionType(types, noSupertypeReduction ? IntersectionFlags.NoSupertypeReduction : 0, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol));
}
return links.resolvedType;
}
function createIndexType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) {
const result = createType(TypeFlags.Index) as IndexType;
result.type = type;
result.indexFlags = indexFlags;
return result;
}
function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) {
const result = createOriginType(TypeFlags.Index) as IndexType;
result.type = type;
return result;
}
function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) {
return indexFlags & IndexFlags.StringsOnly ?
type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, IndexFlags.StringsOnly)) :
type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, IndexFlags.None));
}
/**
* This roughly mirrors `resolveMappedTypeMembers` in the nongeneric case, except only reports a union of the keys calculated,
* rather than manufacturing the properties. We can't just fetch the `constraintType` since that would ignore mappings
* and mapping the `constraintType` directly ignores how mapped types map _properties_ and not keys (thus ignoring subtype
* reduction in the constraintType) when possible.
* @param noIndexSignatures Indicates if _string_ index signatures should be elided. (other index signatures are always reported)
*/
function getIndexTypeForMappedType(type: MappedType, indexFlags: IndexFlags) {
const typeParameter = getTypeParameterFromMappedType(type);
const constraintType = getConstraintTypeFromMappedType(type);
const nameType = getNameTypeFromMappedType(type.target as MappedType || type);
if (!nameType && !(indexFlags & IndexFlags.NoIndexSignatures)) {
// no mapping and no filtering required, just quickly bail to returning the constraint in the common case
return constraintType;
}
const keyTypes: Type[] = [];
// Calling getApparentType on the `T` of a `keyof T` in the constraint type of a generic mapped type can
// trigger a circularity. For example, `T extends { [P in keyof T & string as Captitalize]: any }` is
// a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic.
if (isGenericIndexType(constraintType)) {
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
// We have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer
// the whole `keyof whatever` for later since it's not safe to resolve the shape of modifier type.
return getIndexTypeForGenericType(type, indexFlags);
}
// Include the generic component in the resulting type.
forEachType(constraintType, addMemberForKeyType);
}
else if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, !!(indexFlags & IndexFlags.StringsOnly), addMemberForKeyType);
}
else {
forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
}
// We had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the
// original constraintType, so we can return the union that preserves aliases/origin data if possible.
const result = indexFlags & IndexFlags.NoIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes);
if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)) {
return constraintType;
}
return result;
function addMemberForKeyType(keyType: Type) {
const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType;
// `keyof` currently always returns `string | number` for concrete `string` index signatures - the below ternary keeps that behavior for mapped types
// See `getLiteralTypeFromProperties` where there's a similar ternary to cause the same behavior.
keyTypes.push(propNameType === stringType ? stringOrNumberType : propNameType);
}
}
// Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N
]: X }, to simply N. This however presumes
// that N distributes over union types, i.e. that N is equivalent to N | N | N. Specifically, we only
// want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable
// introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because
// they're the same type regardless of what's being distributed over.
function hasDistributiveNameType(mappedType: MappedType) {
const typeVariable = getTypeParameterFromMappedType(mappedType);
return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable);
function isDistributive(type: Type): boolean {
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true :
type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable :
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) :
type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) :
type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).baseType) && isDistributive((type as SubstitutionType).constraint) :
type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) :
false;
}
}
function getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) {
if (isPrivateIdentifier(name)) {
return neverType;
}
if (isNumericLiteral(name)) {
return getRegularTypeOfLiteralType(checkExpression(name));
}
if (isComputedPropertyName(name)) {
return getRegularTypeOfLiteralType(checkComputedPropertyName(name));
}
const propertyName = getPropertyNameForPropertyNameNode(name);
if (propertyName !== undefined) {
return getStringLiteralType(unescapeLeadingUnderscores(propertyName));
}
if (isExpression(name)) {
return getRegularTypeOfLiteralType(checkExpression(name));
}
return neverType;
}
function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags, includeNonPublic?: boolean) {
if (includeNonPublic || !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) {
let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType;
if (!type) {
const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName | JsxAttributeName;
type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") :
name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined);
}
if (type && type.flags & include) {
return type;
}
}
return neverType;
}
function isKeyTypeIncluded(keyType: Type, include: TypeFlags): boolean {
return !!(keyType.flags & include || keyType.flags & TypeFlags.Intersection && some((keyType as IntersectionType).types, t => isKeyTypeIncluded(t, include)));
}
function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) {
const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createOriginIndexType(type) : undefined;
const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include));
const indexKeyTypes = map(getIndexInfosOfType(type), info =>
info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ?
info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType);
return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin);
}
function shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) {
return !!(type.flags & TypeFlags.InstantiableNonPrimitive ||
isGenericTupleType(type) ||
isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) ||
type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) ||
type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType));
}
function getIndexType(type: Type, indexFlags = IndexFlags.None): Type {
type = getReducedType(type);
return isNoInferType(type) ? getNoInferType(getIndexType((type as SubstitutionType).baseType, indexFlags)) :
shouldDeferIndexType(type, indexFlags) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, indexFlags) :
type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, indexFlags))) :
type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, indexFlags))) :
getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, indexFlags) :
type === wildcardType ? wildcardType :
type.flags & TypeFlags.Unknown ? neverType :
type.flags & (TypeFlags.Any | TypeFlags.Never) ? stringNumberSymbolType :
getLiteralTypeFromProperties(type, (indexFlags & IndexFlags.NoIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (indexFlags & IndexFlags.StringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), indexFlags === IndexFlags.None);
}
function getExtractStringType(type: Type) {
const extractTypeAlias = getGlobalExtractSymbol();
return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType;
}
function getIndexTypeOrString(type: Type): Type {
const indexType = getExtractStringType(getIndexType(type));
return indexType.flags & TypeFlags.Never ? stringType : indexType;
}
function getTypeFromTypeOperatorNode(node: TypeOperatorNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
switch (node.operator) {
case SyntaxKind.KeyOfKeyword:
links.resolvedType = getIndexType(getTypeFromTypeNode(node.type));
break;
case SyntaxKind.UniqueKeyword:
links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword
? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent))
: errorType;
break;
case SyntaxKind.ReadonlyKeyword:
links.resolvedType = getTypeFromTypeNode(node.type);
break;
default:
Debug.assertNever(node.operator);
}
}
return links.resolvedType;
}
function getTypeFromTemplateTypeNode(node: TemplateLiteralTypeNode) {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getTemplateLiteralType(
[node.head.text, ...map(node.templateSpans, span => span.literal.text)],
map(node.templateSpans, span => getTypeFromTypeNode(span.type)),
);
}
return links.resolvedType;
}
function getTemplateLiteralType(texts: readonly string[], types: readonly Type[]): Type {
const unionIndex = findIndex(types, t => !!(t.flags & (TypeFlags.Never | TypeFlags.Union)));
if (unionIndex >= 0) {
return checkCrossProductUnion(types) ?
mapType(types[unionIndex], t => getTemplateLiteralType(texts, replaceElement(types, unionIndex, t))) :
errorType;
}
if (contains(types, wildcardType)) {
return wildcardType;
}
const newTypes: Type[] = [];
const newTexts: string[] = [];
let text = texts[0];
if (!addSpans(texts, types)) {
return stringType;
}
if (newTypes.length === 0) {
return getStringLiteralType(text);
}
newTexts.push(text);
if (every(newTexts, t => t === "")) {
if (every(newTypes, t => !!(t.flags & TypeFlags.String))) {
return stringType;
}
// Normalize `${Mapping}` into Mapping
if (newTypes.length === 1 && isPatternLiteralType(newTypes[0])) {
return newTypes[0];
}
}
const id = `${getTypeListId(newTypes)}|${map(newTexts, t => t.length).join(",")}|${newTexts.join("")}`;
let type = templateLiteralTypes.get(id);
if (!type) {
templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes));
}
return type;
function addSpans(texts: readonly string[], types: readonly Type[]): boolean {
for (let i = 0; i < types.length; i++) {
const t = types[i];
if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) {
text += getTemplateStringForType(t) || "";
text += texts[i + 1];
}
else if (t.flags & TypeFlags.TemplateLiteral) {
text += (t as TemplateLiteralType).texts[0];
if (!addSpans((t as TemplateLiteralType).texts, (t as TemplateLiteralType).types)) return false;
text += texts[i + 1];
}
else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) {
newTypes.push(t);
newTexts.push(text);
text = texts[i + 1];
}
else {
return false;
}
}
return true;
}
}
function getTemplateStringForType(type: Type) {
return type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value :
type.flags & TypeFlags.NumberLiteral ? "" + (type as NumberLiteralType).value :
type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((type as BigIntLiteralType).value) :
type.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) ? (type as IntrinsicType).intrinsicName :
undefined;
}
function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) {
const type = createType(TypeFlags.TemplateLiteral) as TemplateLiteralType;
type.texts = texts;
type.types = types;
return type;
}
function getStringMappingType(symbol: Symbol, type: Type): Type {
return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) :
type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) :
type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) :
// Mapping> === Mapping
type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type :
type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.StringMapping) || isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) :
// This handles Mapping<`${number}`> and Mapping<`${bigint}`>
isPatternLiteralPlaceholderType(type) ? getStringMappingTypeForGenericType(symbol, getTemplateLiteralType(["", ""], [type])) :
type;
}
function applyStringMapping(symbol: Symbol, str: string) {
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
case IntrinsicTypeKind.Uppercase:
return str.toUpperCase();
case IntrinsicTypeKind.Lowercase:
return str.toLowerCase();
case IntrinsicTypeKind.Capitalize:
return str.charAt(0).toUpperCase() + str.slice(1);
case IntrinsicTypeKind.Uncapitalize:
return str.charAt(0).toLowerCase() + str.slice(1);
}
return str;
}
function applyTemplateStringMapping(symbol: Symbol, texts: readonly string[], types: readonly Type[]): [texts: readonly string[], types: readonly Type[]] {
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
case IntrinsicTypeKind.Uppercase:
return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))];
case IntrinsicTypeKind.Lowercase:
return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))];
case IntrinsicTypeKind.Capitalize:
return [texts[0] === "" ? texts : [texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
case IntrinsicTypeKind.Uncapitalize:
return [texts[0] === "" ? texts : [texts[0].charAt(0).toLowerCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types];
}
return [texts, types];
}
function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type {
const id = `${getSymbolId(symbol)},${getTypeId(type)}`;
let result = stringMappingTypes.get(id);
if (!result) {
stringMappingTypes.set(id, result = createStringMappingType(symbol, type));
}
return result;
}
function createStringMappingType(symbol: Symbol, type: Type) {
const result = createTypeWithSymbol(TypeFlags.StringMapping, symbol) as StringMappingType;
result.type = type;
return result;
}
function createIndexedAccessType(objectType: Type, indexType: Type, accessFlags: AccessFlags, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) {
const type = createType(TypeFlags.IndexedAccess) as IndexedAccessType;
type.objectType = objectType;
type.indexType = indexType;
type.accessFlags = accessFlags;
type.aliasSymbol = aliasSymbol;
type.aliasTypeArguments = aliasTypeArguments;
return type;
}
/**
* Returns if a type is or consists of a JSLiteral object type
* In addition to objects which are directly literals,
* * unions where every element is a jsliteral
* * intersections where at least one element is a jsliteral
* * and instantiable types constrained to a jsliteral
* Should all count as literals and not print errors on access or assignment of possibly existing properties.
* This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference).
*/
function isJSLiteralType(type: Type): boolean {
if (noImplicitAny) {
return false; // Flag is meaningless under `noImplicitAny` mode
}
if (getObjectFlags(type) & ObjectFlags.JSLiteral) {
return true;
}
if (type.flags & TypeFlags.Union) {
return every((type as UnionType).types, isJSLiteralType);
}
if (type.flags & TypeFlags.Intersection) {
return some((type as IntersectionType).types, isJSLiteralType);
}
if (type.flags & TypeFlags.Instantiable) {
const constraint = getResolvedBaseConstraint(type);
return constraint !== type && isJSLiteralType(constraint);
}
return false;
}
function getPropertyNameFromIndex(indexType: Type, accessNode: PropertyName | ObjectBindingPattern | ArrayBindingPattern | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) {
return isTypeUsableAsPropertyName(indexType) ?
getPropertyNameFromType(indexType) :
accessNode && isPropertyName(accessNode) ?
// late bound names are handled in the first branch, so here we only need to handle normal names
getPropertyNameForPropertyNameNode(accessNode) :
undefined;
}
function isUncalledFunctionReference(node: Node, symbol: Symbol) {
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) {
const parent = findAncestor(node.parent, n => !isAccessExpression(n)) || node.parent;
if (isCallLikeExpression(parent)) {
return isCallOrNewExpression(parent) && isIdentifier(node) && hasMatchingArgument(parent, node);
}
return every(symbol.declarations, d => !isFunctionLike(d) || isDeprecatedDeclaration(d));
}
return true;
}
function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode);
if (propName !== undefined) {
if (accessFlags & AccessFlags.Contextual) {
return getTypeOfPropertyOfContextualType(objectType, propName) || anyType;
}
const prop = getPropertyOfType(objectType, propName);
if (prop) {
if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && isDeprecatedSymbol(prop) && isUncalledFunctionReference(accessNode, prop)) {
const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode);
addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string);
}
if (accessExpression) {
markPropertyAsReferenced(prop, accessExpression, isSelfTypeAccess(accessExpression.expression, objectType.symbol));
if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) {
error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop));
return undefined;
}
if (accessFlags & AccessFlags.CacheSymbol) {
getNodeLinks(accessNode!).resolvedSymbol = prop;
}
if (isThisPropertyAccessInConstructor(accessExpression, prop)) {
return autoType;
}
}
const propType = accessFlags & AccessFlags.Writing ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop);
return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? getFlowTypeOfReference(accessExpression, propType) :
accessNode && isIndexedAccessTypeNode(accessNode) && containsMissingType(propType) ? getUnionType([propType, undefinedType]) :
propType;
}
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName)) {
const index = +propName;
if (accessNode && everyType(objectType, t => !((t as TupleTypeReference).target.combinedFlags & ElementFlags.Variable)) && !(accessFlags & AccessFlags.AllowMissing)) {
const indexNode = getIndexNodeForAccessExpression(accessNode);
if (isTupleType(objectType)) {
if (index < 0) {
error(indexNode, Diagnostics.A_tuple_type_cannot_be_indexed_with_a_negative_value);
return undefinedType;
}
error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2, typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName));
}
else {
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
}
}
if (index >= 0) {
errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType));
return getTupleElementTypeOutOfStartCount(objectType, index, accessFlags & AccessFlags.IncludeUndefined ? missingType : undefined);
}
}
}
if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) {
if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) {
return objectType;
}
// If no index signature is applicable, we default to the string index signature. In effect, this means the string
// index signature applies even when accessing with a symbol-like type.
const indexInfo = getApplicableIndexInfo(objectType, indexType) || getIndexInfoOfType(objectType, stringType);
if (indexInfo) {
if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo.keyType !== numberType) {
if (accessExpression) {
if (accessFlags & AccessFlags.Writing) {
error(accessExpression, Diagnostics.Type_0_is_generic_and_can_only_be_indexed_for_reading, typeToString(originalObjectType));
}
else {
error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType));
}
}
return undefined;
}
if (accessNode && indexInfo.keyType === stringType && !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 accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, missingType]) : indexInfo.type;
}
errorIfWritingToReadonlyIndex(indexInfo);
// When accessing an enum object with its own type,
// e.g. E[E.A] for enum E { A }, undefined shouldn't
// be included in the result type
if (
(accessFlags & AccessFlags.IncludeUndefined) &&
!(objectType.symbol &&
objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum) &&
(indexType.symbol &&
indexType.flags & TypeFlags.EnumLiteral &&
getParentOfSymbol(indexType.symbol) === objectType.symbol))
) {
return getUnionType([indexInfo.type, missingType]);
}
return indexInfo.type;
}
if (indexType.flags & TypeFlags.Never) {
return neverType;
}
if (isJSLiteralType(objectType)) {
return anyType;
}
if (accessExpression && !isConstEnumObjectType(objectType)) {
if (isObjectLiteralType(objectType)) {
if (noImplicitAny && indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
diagnostics.add(createDiagnosticForNode(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType)));
return undefinedType;
}
else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) {
const types = map((objectType as ResolvedType).properties, property => {
return getTypeOfSymbol(property);
});
return getUnionType(append(types, undefinedType));
}
}
if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) {
error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
}
else if (noImplicitAny && !(accessFlags & AccessFlags.SuppressNoImplicitAnyError)) {
if (propName !== undefined && typeHasStaticProperty(propName, objectType)) {
const typeName = typeToString(objectType);
error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName as string, typeName, typeName + "[" + getTextOfNode(accessExpression.argumentExpression) + "]");
}
else if (getIndexTypeOfType(objectType, numberType)) {
error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number);
}
else {
let suggestion: string | undefined;
if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) {
if (suggestion !== undefined) {
error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion);
}
}
else {
const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType);
if (suggestion !== undefined) {
error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion);
}
else {
let errorInfo: DiagnosticMessageChain | undefined;
if (indexType.flags & TypeFlags.EnumLiteral) {
errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType));
}
else if (indexType.flags & TypeFlags.UniqueESSymbol) {
const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression);
errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType));
}
else if (indexType.flags & TypeFlags.StringLiteral) {
errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType));
}
else if (indexType.flags & TypeFlags.NumberLiteral) {
errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType));
}
else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) {
errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType));
}
errorInfo = chainDiagnosticMessages(
errorInfo,
Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1,
typeToString(fullIndexType),
typeToString(objectType),
);
diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(accessExpression), accessExpression, errorInfo));
}
}
}
}
return undefined;
}
}
if (accessFlags & AccessFlags.AllowMissing && isObjectLiteralType(objectType)) {
return undefinedType;
}
if (isJSLiteralType(objectType)) {
return anyType;
}
if (accessNode) {
const indexNode = getIndexNodeForAccessExpression(accessNode);
if (indexNode.kind !== SyntaxKind.BigIntLiteral && indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType as StringLiteralType | NumberLiteralType).value, typeToString(objectType));
}
else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) {
error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType));
}
else {
const typeString = indexNode.kind === SyntaxKind.BigIntLiteral ? "bigint" : typeToString(indexType);
error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeString);
}
}
if (isTypeAny(indexType)) {
return indexType;
}
return undefined;
function errorIfWritingToReadonlyIndex(indexInfo: IndexInfo | undefined): void {
if (indexInfo && indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) {
error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));
}
}
}
function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) {
return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression :
accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType :
accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression :
accessNode;
}
function isPatternLiteralPlaceholderType(type: Type): boolean {
if (type.flags & TypeFlags.Intersection) {
// Return true if the intersection consists of one or more placeholders and zero or
// more object type tags.
let seenPlaceholder = false;
for (const t of (type as IntersectionType).types) {
if (t.flags & (TypeFlags.Literal | TypeFlags.Nullable) || isPatternLiteralPlaceholderType(t)) {
seenPlaceholder = true;
}
else if (!(t.flags & TypeFlags.Object)) {
return false;
}
}
return seenPlaceholder;
}
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type);
}
function isPatternLiteralType(type: Type) {
// A pattern literal type is a template literal or a string mapping type that contains only
// non-generic pattern literal placeholders.
return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) ||
!!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type);
}
function isGenericStringLikeType(type: Type) {
return !!(type.flags & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) && !isPatternLiteralType(type);
}
function isGenericType(type: Type): boolean {
return !!getGenericObjectFlags(type);
}
function isGenericObjectType(type: Type): boolean {
return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType);
}
function isGenericIndexType(type: Type): boolean {
return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericIndexType);
}
function getGenericObjectFlags(type: Type): ObjectFlags {
if (type.flags & (TypeFlags.UnionOrIntersection)) {
if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
(type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0);
}
return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType;
}
if (type.flags & TypeFlags.Substitution) {
if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) {
(type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericTypeComputed |
getGenericObjectFlags((type as SubstitutionType).baseType) | getGenericObjectFlags((type as SubstitutionType).constraint);
}
return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType;
}
return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) |
(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index) || isGenericStringLikeType(type) ? ObjectFlags.IsGenericIndexType : 0);
}
function getSimplifiedType(type: Type, writing: boolean): Type {
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) :
type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) :
type;
}
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.Union || objectType.flags & TypeFlags.Intersection && !shouldDeferIndexType(objectType)) {
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. 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[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, 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)) {
// (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 (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]
// A generic tuple type indexed by a number exists only when the index type doesn't select a
// fixed element. We simplify to either the combined type of all elements (when the index type
// the actual number type) or to the combined type of all non-fixed elements.
if (isGenericTupleType(objectType) && indexType.flags & TypeFlags.NumberLike) {
const elementType = getElementTypeOfSliceOfTupleType(objectType, indexType.flags & TypeFlags.Number ? 0 : objectType.target.fixedLength, /*endSkipCount*/ 0, writing);
if (elementType) {
return type[cache] = elementType;
}
}
// If the object type is a mapped type { [P in K]: E }, where K is generic, or { [P in K as N]: E }, where
// K is generic and N is assignable to P, instantiate E using a mapper that substitutes the index type for P.
// For example, for an index access { [P in K]: Box }[X], we construct the type Box.
if (isGenericMappedType(objectType)) {
if (getMappedTypeNameTypeKind(objectType) !== MappedTypeNameTypeKind.Remapping) {
return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing));
}
}
return type[cache] = type;
}
function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) {
const checkType = type.checkType;
const extendsType = type.extendsType;
const trueType = getTrueTypeFromConditionalType(type);
const falseType = getFalseTypeFromConditionalType(type);
// Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`.
if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) {
if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
return getSimplifiedType(trueType, writing);
}
else if (isIntersectionEmpty(checkType, extendsType)) { // Always false
return neverType;
}
}
else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) {
if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
return neverType;
}
else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false
return getSimplifiedType(falseType, writing);
}
}
return type;
}
/**
* Invokes union simplification logic to determine if an intersection is considered empty as a union constituent
*/
function isIntersectionEmpty(type1: Type, type2: Type) {
return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never);
}
// Given an indexed access on a mapped type of the form { [P in K]: E }[X], return an instantiation of E where P is
// replaced with X. Since this simplification doesn't account for mapped type modifiers, add 'undefined' to the
// resulting type if the mapped type includes a '?' modifier or if the modifiers type indicates that some properties
// are optional. If the modifiers type is generic, conservatively estimate optionality by recursively looking for
// mapped types that include '?' modifiers.
function substituteIndexedMappedType(objectType: MappedType, index: Type) {
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]);
const templateMapper = combineTypeMappers(objectType.mapper, mapper);
const instantiatedTemplateType = instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper);
const isOptional = getMappedTypeOptionality(objectType) > 0 || (isGenericType(objectType) ?
getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(objectType)) > 0 :
couldAccessOptionalProperty(objectType, index));
return addOptionality(instantiatedTemplateType, /*isProperty*/ true, isOptional);
}
// Return true if an indexed access with the given object and index types could access an optional property.
function couldAccessOptionalProperty(objectType: Type, indexType: Type) {
const indexConstraint = getBaseConstraintOfType(indexType);
return !!indexConstraint && some(getPropertiesOfType(objectType), p =>
!!(p.flags & SymbolFlags.Optional) &&
isTypeAssignableTo(getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique), indexConstraint));
}
function getIndexedAccessType(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
return getIndexedAccessTypeOrUndefined(objectType, indexType, accessFlags, accessNode, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType);
}
function indexTypeLessThan(indexType: Type, limit: number) {
return everyType(indexType, t => {
if (t.flags & TypeFlags.StringOrNumberLiteral) {
const propName = getPropertyNameFromType(t as StringLiteralType | NumberLiteralType);
if (isNumericLiteralName(propName)) {
const index = +propName;
return index >= 0 && index < limit;
}
}
return false;
});
}
function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined {
if (objectType === wildcardType || indexType === wildcardType) {
return wildcardType;
}
objectType = getReducedType(objectType);
// 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;
}
// In noUncheckedIndexedAccess mode, indexed access operations that occur in an expression in a read position and resolve to
// an index signature have 'undefined' included in their type.
if (compilerOptions.noUncheckedIndexedAccess && accessFlags & AccessFlags.ExpressionPosition) accessFlags |= AccessFlags.IncludeUndefined;
// If the index type is generic, or if the object type is generic and doesn't originate in an expression and
// the operation isn't exclusively indexing the fixed (non-variadic) portion of a tuple type, 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 an expression. This is to
// preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved
// eagerly using the constraint type of 'this' at the given location.
if (
isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ?
isGenericTupleType(objectType) && !indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target)) :
isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target))) || isGenericReducibleType(objectType))
) {
if (objectType.flags & TypeFlags.AnyOrUnknown) {
return objectType;
}
// Defer the operation by creating an indexed access type.
const persistentAccessFlags = accessFlags & AccessFlags.Persistent;
const id = objectType.id + "," + indexType.id + "," + persistentAccessFlags + getAliasId(aliasSymbol, aliasTypeArguments);
let type = indexedAccessTypes.get(id);
if (!type) {
indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, persistentAccessFlags, aliasSymbol, aliasTypeArguments));
}
return type;
}
// In the following we resolve T[K] to the type of the property in T selected by K.
// We treat boolean as different from other unions to improve errors;
// skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'.
const apparentObjectType = getReducedApparentType(objectType);
if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) {
const propTypes: Type[] = [];
let wasMissingProp = false;
for (const t of (indexType as UnionType).types) {
const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, accessNode, accessFlags | (wasMissingProp ? AccessFlags.SuppressNoImplicitAnyError : 0));
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;
}
}
if (wasMissingProp) {
return undefined;
}
return accessFlags & AccessFlags.Writing
? getIntersectionType(propTypes, IntersectionFlags.None, aliasSymbol, aliasTypeArguments)
: getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
}
return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated);
}
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const objectType = getTypeFromTypeNode(node.objectType);
const indexType = getTypeFromTypeNode(node.indexType);
const potentialAlias = getAliasSymbolForTypeNode(node);
links.resolvedType = getIndexedAccessType(objectType, indexType, AccessFlags.None, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias));
}
return links.resolvedType;
}
function getTypeFromMappedTypeNode(node: MappedTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const type = createObjectType(ObjectFlags.Mapped, node.symbol) as MappedType;
type.declaration = node;
type.aliasSymbol = getAliasSymbolForTypeNode(node);
type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol);
links.resolvedType = type;
// Eagerly resolve the constraint type which forces an error if the constraint type circularly
// references itself through one or more type aliases.
getConstraintTypeFromMappedType(type);
}
return links.resolvedType;
}
function getActualTypeVariable(type: Type): Type {
if (type.flags & TypeFlags.Substitution) {
return getActualTypeVariable((type as SubstitutionType).baseType);
}
if (
type.flags & TypeFlags.IndexedAccess && (
(type as IndexedAccessType).objectType.flags & TypeFlags.Substitution ||
(type as IndexedAccessType).indexType.flags & TypeFlags.Substitution
)
) {
return getIndexedAccessType(getActualTypeVariable((type as IndexedAccessType).objectType), getActualTypeVariable((type as IndexedAccessType).indexType));
}
return type;
}
function isSimpleTupleType(node: TypeNode): boolean {
return isTupleTypeNode(node) && length(node.elements) > 0 &&
!some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken));
}
function isDeferredType(type: Type, checkTuples: boolean) {
return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType);
}
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
let result;
let extraTypes: Type[] | undefined;
let tailCount = 0;
// We loop here for an immediately nested conditional type in the false position, effectively treating
// types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for
// purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of
// another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive
// cases we increment the tail recursion counter and stop after 1000 iterations.
while (true) {
if (tailCount === 1000) {
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
return errorType;
}
const checkType = instantiateType(getActualTypeVariable(root.checkType), mapper);
const extendsType = instantiateType(root.extendsType, mapper);
if (checkType === errorType || extendsType === errorType) {
return errorType;
}
if (checkType === wildcardType || extendsType === wildcardType) {
return wildcardType;
}
const checkTypeNode = skipTypeParentheses(root.node.checkType);
const extendsTypeNode = skipTypeParentheses(root.node.extendsType);
// When the check and extends types are simple tuple types of the same arity, we defer resolution of the
// conditional type when any tuple elements are generic. This is such that non-distributable conditional
// types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`.
const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) &&
length((checkTypeNode as TupleTypeNode).elements) === length((extendsTypeNode as TupleTypeNode).elements);
const checkTypeDeferred = isDeferredType(checkType, checkTuples);
let combinedMapper: TypeMapper | undefined;
if (root.inferTypeParameters) {
// When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be
// instantiated with the context, so it doesn't need the mapper for the inference context - however the constraint
// may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2].
// [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated
// as `number`
// Conversely, if we have `Foo`, `B` is still constrained to `T` and `T` is instantiated as `A`
// [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T`
// which is in turn instantiated as `Q`, which is in turn instantiated as `number`.
// So we need to:
// * combine `context.nonFixingMapper` with `mapper` so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information)
// * incorporate all of the component mappers into the combined mapper for the true and false members
// This means we have two mappers that need applying:
// * The original `mapper` used to create this conditional
// * The mapper that maps the infer type parameter to its inference result (`context.mapper`)
const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None);
if (mapper) {
context.nonFixingMapper = combineTypeMappers(context.nonFixingMapper, mapper);
}
if (!checkTypeDeferred) {
// We don't want inferences from constraints as they may cause us to eagerly resolve the
// conditional type instead of deferring resolution. Also, we always want strict function
// types rules (i.e. proper contravariance) for inferences.
inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
}
// It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the
// those type parameters are used in type references (see getInferredTypeParameterConstraint). For
// that reason we need context.mapper to be first in the combined mapper. See #42636 for examples.
combinedMapper = mapper ? combineTypeMappers(context.mapper, mapper) : context.mapper;
}
// Instantiate the extends type including inferences for 'infer T' type parameters
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
// We attempt to resolve the conditional type only when the check and extends types are non-generic
if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) {
// Return falseType for a definitely false extends check. We check an instantiations of the two
// types with type parameters mapped to the wildcard type, the most permissive instantiations
// possible (the wildcard type is assignable to and from all types). If those are not related,
// then no instantiations will be and we can just return the false branch type.
if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) {
// Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a
// distributive conditional type applied to the constraint of a type variable, include trueType if
// there are possible values of the check type that are also possible values of the extends type.
// We use a reverse assignability check as it is less expensive than the comparable relationship
// and avoids false positives of a non-empty intersection check.
if (checkType.flags & TypeFlags.Any || forConstraint && !(inferredExtendsType.flags & TypeFlags.Never) && someType(getPermissiveInstantiation(inferredExtendsType), t => isTypeAssignableTo(t, getPermissiveInstantiation(checkType)))) {
(extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper));
}
// If falseType is an immediately nested conditional type that isn't distributive or has an
// identical checkType, switch to that type and loop.
const falseType = getTypeFromTypeNode(root.node.falseType);
if (falseType.flags & TypeFlags.Conditional) {
const newRoot = (falseType as ConditionalType).root;
if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) {
root = newRoot;
continue;
}
if (canTailRecurse(falseType, mapper)) {
continue;
}
}
result = instantiateType(falseType, mapper);
break;
}
// Return trueType for a definitely true extends check. We check instantiations of the two
// types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
// that has no constraint. This ensures that, for example, the type
// type Foo = T extends { x: string } ? string : number
// doesn't immediately resolve to 'string' instead of being deferred.
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
const trueType = getTypeFromTypeNode(root.node.trueType);
const trueMapper = combinedMapper || mapper;
if (canTailRecurse(trueType, trueMapper)) {
continue;
}
result = instantiateType(trueType, trueMapper);
break;
}
}
// Return a deferred type for a check that is neither definitely true nor definitely false
result = createType(TypeFlags.Conditional) as ConditionalType;
result.root = root;
result.checkType = instantiateType(root.checkType, mapper);
result.extendsType = instantiateType(root.extendsType, mapper);
result.mapper = mapper;
result.combinedMapper = combinedMapper;
result.aliasSymbol = aliasSymbol || root.aliasSymbol;
result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217
break;
}
return extraTypes ? getUnionType(append(extraTypes, result)) : result;
// We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and
// (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check
// type. Note that recursion is possible only through aliased conditional types, so we only increment the tail
// recursion counter for those.
function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) {
if (newType.flags & TypeFlags.Conditional && newMapper) {
const newRoot = (newType as ConditionalType).root;
if (newRoot.outerTypeParameters) {
const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper);
const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper));
const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments);
const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined;
if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) {
root = newRoot;
mapper = newRootMapper;
aliasSymbol = undefined;
aliasTypeArguments = undefined;
if (newRoot.aliasSymbol) {
tailCount++;
}
return true;
}
}
}
return false;
}
}
function getTrueTypeFromConditionalType(type: ConditionalType) {
return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper));
}
function getFalseTypeFromConditionalType(type: ConditionalType) {
return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper));
}
function getInferredTrueTypeFromConditionalType(type: ConditionalType) {
return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type));
}
function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined {
let result: TypeParameter[] | undefined;
if (node.locals) {
node.locals.forEach(symbol => {
if (symbol.flags & SymbolFlags.TypeParameter) {
result = append(result, getDeclaredTypeOfSymbol(symbol));
}
});
}
return result;
}
function isDistributionDependent(root: ConditionalRoot) {
return root.isDistributive && (
isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.trueType) ||
isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.falseType)
);
}
function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const checkType = getTypeFromTypeNode(node.checkType);
const aliasSymbol = getAliasSymbolForTypeNode(node);
const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol);
const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true);
const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node));
const root: ConditionalRoot = {
node,
checkType,
extendsType: getTypeFromTypeNode(node.extendsType),
isDistributive: !!(checkType.flags & TypeFlags.TypeParameter),
inferTypeParameters: getInferTypeParameters(node),
outerTypeParameters,
instantiations: undefined,
aliasSymbol,
aliasTypeArguments,
};
links.resolvedType = getConditionalType(root, /*mapper*/ undefined, /*forConstraint*/ false);
if (outerTypeParameters) {
root.instantiations = new Map();
root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType);
}
}
return links.resolvedType;
}
function getTypeFromInferTypeNode(node: InferTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter));
}
return links.resolvedType;
}
function getIdentifierChain(node: EntityName): Identifier[] {
if (isIdentifier(node)) {
return [node];
}
else {
return append(getIdentifierChain(node.left), node.right);
}
}
function getTypeFromImportTypeNode(node: ImportTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
if (!isLiteralImportTypeNode(node)) {
error(node.argument, Diagnostics.String_literal_expected);
links.resolvedSymbol = unknownSymbol;
return links.resolvedType = errorType;
}
const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type;
// TODO: Future work: support unions/generics/whatever via a deferred import-type
const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal);
if (!innerModuleSymbol) {
links.resolvedSymbol = unknownSymbol;
return links.resolvedType = errorType;
}
const isExportEquals = !!innerModuleSymbol.exports?.get(InternalSymbolName.ExportEquals);
const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false);
if (!nodeIsMissing(node.qualifier)) {
const nameStack: Identifier[] = getIdentifierChain(node.qualifier!);
let currentNamespace = moduleSymbol;
let current: Identifier | undefined;
while (current = nameStack.shift()) {
const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning;
// typeof a.b.c is normally resolved using `checkExpression` which in turn defers to `checkQualifiedName`
// That, in turn, ultimately uses `getPropertyOfType` on the type of the symbol, which differs slightly from
// the `exports` lookup process that only looks up namespace members which is used for most type references
const mergedResolvedSymbol = getMergedSymbol(resolveSymbol(currentNamespace));
const symbolFromVariable = node.isTypeOf || isInJSFile(node) && isExportEquals
? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText, /*skipObjectFunctionPropertyAugment*/ false, /*includeTypeOnlyMembers*/ true)
: undefined;
const symbolFromModule = node.isTypeOf ? undefined : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning);
const next = symbolFromModule ?? symbolFromVariable;
if (!next) {
error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current));
return links.resolvedType = errorType;
}
getNodeLinks(current).resolvedSymbol = next;
getNodeLinks(current.parent).resolvedSymbol = next;
currentNamespace = next;
}
links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning);
}
else {
if (moduleSymbol.flags & targetMeaning) {
links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning);
}
else {
const errorMessage = targetMeaning === SymbolFlags.Value
? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here
: Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0;
error(node, errorMessage, node.argument.literal.text);
links.resolvedSymbol = unknownSymbol;
links.resolvedType = errorType;
}
}
}
return links.resolvedType;
}
function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) {
const resolvedSymbol = resolveSymbol(symbol);
links.resolvedSymbol = resolvedSymbol;
if (meaning === SymbolFlags.Value) {
return getInstantiationExpressionType(getTypeOfSymbol(symbol), node); // intentionally doesn't use resolved symbol so type is cached as expected on the alias
}
else {
return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol
}
}
function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
// Deferred resolution of members is handled by resolveObjectTypeMembers
const aliasSymbol = getAliasSymbolForTypeNode(node);
if (!node.symbol || getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) {
links.resolvedType = emptyTypeLiteralType;
}
else {
let type = createObjectType(ObjectFlags.Anonymous, node.symbol);
type.aliasSymbol = aliasSymbol;
type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol);
if (isJSDocTypeLiteral(node) && node.isArrayType) {
type = createArrayType(type);
}
links.resolvedType = type;
}
}
return links.resolvedType;
}
function getAliasSymbolForTypeNode(node: Node) {
let host = node.parent;
while (isParenthesizedTypeNode(host) || isJSDocTypeExpression(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) {
host = host.parent;
}
return isTypeAlias(host) ? getSymbolOfDeclaration(host) : undefined;
}
function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) {
return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined;
}
function isNonGenericObjectType(type: Type) {
return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type);
}
function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) {
return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index));
}
function tryMergeUnionOfObjectTypeAndEmptyObject(type: Type, readonly: boolean): Type {
if (!(type.flags & TypeFlags.Union)) {
return type;
}
if (every((type as UnionType).types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) {
return find((type as UnionType).types, isEmptyObjectType) || emptyObjectType;
}
const firstType = find((type as UnionType).types, t => !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t));
if (!firstType) {
return type;
}
const secondType = find((type as UnionType).types, t => t !== firstType && !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t));
if (secondType) {
return type;
}
return getAnonymousPartialType(firstType);
function getAnonymousPartialType(type: Type) {
// gets the type as if it had been spread, but where everything in the spread is made optional
const members = createSymbolTable();
for (const prop of getPropertiesOfType(type)) {
if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) {
// do nothing, skip privates
}
else if (isSpreadableProperty(prop)) {
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
const flags = SymbolFlags.Property | SymbolFlags.Optional;
const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0));
result.links.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true);
result.declarations = prop.declarations;
result.links.nameType = getSymbolLinks(prop).nameType;
result.links.syntheticOrigin = prop;
members.set(prop.escapedName, result);
}
}
const spread = createAnonymousType(type.symbol, members, emptyArray, emptyArray, getIndexInfosOfType(type));
spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
return spread;
}
}
/**
* Since the source of spread types are object literals, which are not binary,
* this function should be called in a left folding style, with left = previous result of getSpreadType
* and right = the new element to be spread.
*/
function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): Type {
if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) {
return anyType;
}
if (left.flags & TypeFlags.Unknown || right.flags & TypeFlags.Unknown) {
return unknownType;
}
if (left.flags & TypeFlags.Never) {
return right;
}
if (right.flags & TypeFlags.Never) {
return left;
}
left = tryMergeUnionOfObjectTypeAndEmptyObject(left, readonly);
if (left.flags & TypeFlags.Union) {
return checkCrossProductUnion([left, right])
? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly))
: errorType;
}
right = tryMergeUnionOfObjectTypeAndEmptyObject(right, readonly);
if (right.flags & TypeFlags.Union) {
return checkCrossProductUnion([left, right])
? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly))
: errorType;
}
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) {
return left;
}
if (isGenericObjectType(left) || isGenericObjectType(right)) {
if (isEmptyObjectType(left)) {
return right;
}
// When the left type is an intersection, we may need to merge the last constituent of the
// intersection with the right type. For example when the left type is 'T & { a: string }'
// and the right type is '{ b: string }' we produce 'T & { a: string, b: string }'.
if (left.flags & TypeFlags.Intersection) {
const types = (left as IntersectionType).types;
const lastLeft = types[types.length - 1];
if (isNonGenericObjectType(lastLeft) && isNonGenericObjectType(right)) {
return getIntersectionType(concatenate(types.slice(0, types.length - 1), [getSpreadType(lastLeft, right, symbol, objectFlags, readonly)]));
}
}
return getIntersectionType([left, right]);
}
const members = createSymbolTable();
const skippedPrivateMembers = new Set<__String>();
const indexInfos = left === emptyObjectType ? getIndexInfosOfType(right) : getUnionIndexInfos([left, right]);
for (const rightProp of getPropertiesOfType(right)) {
if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) {
skippedPrivateMembers.add(rightProp.escapedName);
}
else if (isSpreadableProperty(rightProp)) {
members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly));
}
}
for (const leftProp of getPropertiesOfType(left)) {
if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) {
continue;
}
if (members.has(leftProp.escapedName)) {
const rightProp = members.get(leftProp.escapedName)!;
const rightType = getTypeOfSymbol(rightProp);
if (rightProp.flags & SymbolFlags.Optional) {
const declarations = concatenate(leftProp.declarations, rightProp.declarations);
const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional);
const result = createSymbol(flags, leftProp.escapedName);
// Optimization: avoid calculating the union type if spreading into the exact same type.
// This is common, e.g. spreading one options bag into another where the bags have the
// same type, or have properties which overlap. If the unions are large, it may turn out
// to be expensive to perform subtype reduction.
const leftType = getTypeOfSymbol(leftProp);
const leftTypeWithoutUndefined = removeMissingOrUndefinedType(leftType);
const rightTypeWithoutUndefined = removeMissingOrUndefinedType(rightType);
result.links.type = leftTypeWithoutUndefined === rightTypeWithoutUndefined ? leftType : getUnionType([leftType, rightTypeWithoutUndefined], UnionReduction.Subtype);
result.links.leftSpread = leftProp;
result.links.rightSpread = rightProp;
result.declarations = declarations;
result.links.nameType = getSymbolLinks(leftProp).nameType;
members.set(leftProp.escapedName, result);
}
}
else {
members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly));
}
}
const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, sameMap(indexInfos, info => getIndexInfoWithReadonly(info, readonly)));
spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags;
return spread;
}
/** We approximate own properties as non-methods plus methods that are inside the object literal */
function isSpreadableProperty(prop: Symbol): boolean {
return !some(prop.declarations, isPrivateIdentifierClassElementDeclaration) &&
(!(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) ||
!prop.declarations?.some(decl => isClassLike(decl.parent)));
}
function getSpreadSymbol(prop: Symbol, readonly: boolean) {
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
if (!isSetonlyAccessor && readonly === isReadonlySymbol(prop)) {
return prop;
}
const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional);
const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0));
result.links.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop);
result.declarations = prop.declarations;
result.links.nameType = getSymbolLinks(prop).nameType;
result.links.syntheticOrigin = prop;
return result;
}
function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) {
return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration, info.components) : info;
}
function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol?: Symbol, regularType?: LiteralType) {
const type = createTypeWithSymbol(flags, symbol!) as LiteralType;
type.value = value;
type.regularType = regularType || type;
return type;
}
function getFreshTypeOfLiteralType(type: Type): Type {
if (type.flags & TypeFlags.Freshable) {
if (!(type as FreshableType).freshType) {
const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType);
freshType.freshType = freshType;
(type as FreshableType).freshType = freshType;
}
return (type as FreshableType).freshType;
}
return type;
}
function getRegularTypeOfLiteralType(type: Type): Type {
return type.flags & TypeFlags.Freshable ? (type as FreshableType).regularType :
type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) :
type;
}
function isFreshLiteralType(type: Type) {
return !!(type.flags & TypeFlags.Freshable) && (type as LiteralType).freshType === type;
}
function getStringLiteralType(value: string): StringLiteralType {
let type;
return stringLiteralTypes.get(value) ||
(stringLiteralTypes.set(value, type = createLiteralType(TypeFlags.StringLiteral, value) as StringLiteralType), type);
}
function getNumberLiteralType(value: number): NumberLiteralType {
let type;
return numberLiteralTypes.get(value) ||
(numberLiteralTypes.set(value, type = createLiteralType(TypeFlags.NumberLiteral, value) as NumberLiteralType), type);
}
function getBigIntLiteralType(value: PseudoBigInt): BigIntLiteralType {
let type;
const key = pseudoBigIntToString(value);
return bigIntLiteralTypes.get(key) ||
(bigIntLiteralTypes.set(key, type = createLiteralType(TypeFlags.BigIntLiteral, value) as BigIntLiteralType), type);
}
function getEnumLiteralType(value: string | number, enumId: number, symbol: Symbol): LiteralType {
let type;
const key = `${enumId}${typeof value === "string" ? "@" : "#"}${value}`;
const flags = TypeFlags.EnumLiteral | (typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.NumberLiteral);
return enumLiteralTypes.get(key) ||
(enumLiteralTypes.set(key, type = createLiteralType(flags, value, symbol)), type);
}
function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type {
if (node.literal.kind === SyntaxKind.NullKeyword) {
return nullType;
}
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal));
}
return links.resolvedType;
}
function createUniqueESSymbolType(symbol: Symbol) {
const type = createTypeWithSymbol(TypeFlags.UniqueESSymbol, symbol) as UniqueESSymbolType;
type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String;
return type;
}
function getESSymbolLikeTypeForNode(node: Node) {
if (isInJSFile(node) && isJSDocTypeExpression(node)) {
const host = getJSDocHost(node);
if (host) {
node = getSingleVariableOfVariableStatement(host) || host;
}
}
if (isValidESSymbolDeclaration(node)) {
const symbol = isCommonJsExportPropertyAssignment(node) ? getSymbolOfNode((node as BinaryExpression).left) : getSymbolOfNode(node);
if (symbol) {
const links = getSymbolLinks(symbol);
return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol));
}
}
return esSymbolType;
}
function getThisType(node: Node): Type {
const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
const parent = container && container.parent;
if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) {
if (
!isStatic(container) &&
(!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body))
) {
return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!;
}
}
// inside x.prototype = { ... }
if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) {
return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!;
}
// /** @return {this} */
// x.prototype.m = function() { ... }
const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined;
if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) {
return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!;
}
// inside constructor function C() { ... }
if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) {
return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(container)).thisType!;
}
error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface);
return errorType;
}
function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getThisType(node);
}
return links.resolvedType;
}
function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) {
return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type);
}
function getArrayElementTypeNode(node: TypeNode): TypeNode | undefined {
switch (node.kind) {
case SyntaxKind.ParenthesizedType:
return getArrayElementTypeNode((node as ParenthesizedTypeNode).type);
case SyntaxKind.TupleType:
if ((node as TupleTypeNode).elements.length === 1) {
node = (node as TupleTypeNode).elements[0];
if (node.kind === SyntaxKind.RestType || node.kind === SyntaxKind.NamedTupleMember && (node as NamedTupleMember).dotDotDotToken) {
return getArrayElementTypeNode((node as RestTypeNode | NamedTupleMember).type);
}
}
break;
case SyntaxKind.ArrayType:
return (node as ArrayTypeNode).elementType;
}
return undefined;
}
function getTypeFromNamedTupleTypeNode(node: NamedTupleMember): Type {
const links = getNodeLinks(node);
return links.resolvedType || (links.resolvedType = node.dotDotDotToken ? getTypeFromRestTypeNode(node) :
addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true, !!node.questionToken));
}
function getTypeFromTypeNode(node: TypeNode): Type {
return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node);
}
function getTypeFromTypeNodeWorker(node: TypeNode): Type {
switch (node.kind) {
case SyntaxKind.AnyKeyword:
case SyntaxKind.JSDocAllType:
case SyntaxKind.JSDocUnknownType:
return anyType;
case SyntaxKind.UnknownKeyword:
return unknownType;
case SyntaxKind.StringKeyword:
return stringType;
case SyntaxKind.NumberKeyword:
return numberType;
case SyntaxKind.BigIntKeyword:
return bigintType;
case SyntaxKind.BooleanKeyword:
return booleanType;
case SyntaxKind.SymbolKeyword:
return esSymbolType;
case SyntaxKind.VoidKeyword:
return voidType;
case SyntaxKind.UndefinedKeyword:
return undefinedType;
case SyntaxKind.NullKeyword as TypeNodeSyntaxKind:
// TODO(rbuckton): `NullKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service.
return nullType;
case SyntaxKind.NeverKeyword:
return neverType;
case SyntaxKind.ObjectKeyword:
return node.flags & NodeFlags.JavaScriptFile && !noImplicitAny ? anyType : nonPrimitiveType;
case SyntaxKind.IntrinsicKeyword:
return intrinsicMarkerType;
case SyntaxKind.ThisType:
case SyntaxKind.ThisKeyword as TypeNodeSyntaxKind:
// TODO(rbuckton): `ThisKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service and because of `isPartOfTypeNode`.
return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode);
case SyntaxKind.LiteralType:
return getTypeFromLiteralTypeNode(node as LiteralTypeNode);
case SyntaxKind.TypeReference:
return getTypeFromTypeReference(node as TypeReferenceNode);
case SyntaxKind.TypePredicate:
return (node as TypePredicateNode).assertsModifier ? voidType : booleanType;
case SyntaxKind.ExpressionWithTypeArguments:
return getTypeFromTypeReference(node as ExpressionWithTypeArguments);
case SyntaxKind.TypeQuery:
return getTypeFromTypeQueryNode(node as TypeQueryNode);
case SyntaxKind.ArrayType:
case SyntaxKind.TupleType:
return getTypeFromArrayOrTupleTypeNode(node as ArrayTypeNode | TupleTypeNode);
case SyntaxKind.OptionalType:
return getTypeFromOptionalTypeNode(node as OptionalTypeNode);
case SyntaxKind.UnionType:
return getTypeFromUnionTypeNode(node as UnionTypeNode);
case SyntaxKind.IntersectionType:
return getTypeFromIntersectionTypeNode(node as IntersectionTypeNode);
case SyntaxKind.JSDocNullableType:
return getTypeFromJSDocNullableTypeNode(node as JSDocNullableType);
case SyntaxKind.JSDocOptionalType:
return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type));
case SyntaxKind.NamedTupleMember:
return getTypeFromNamedTupleTypeNode(node as NamedTupleMember);
case SyntaxKind.ParenthesizedType:
case SyntaxKind.JSDocNonNullableType:
case SyntaxKind.JSDocTypeExpression:
return getTypeFromTypeNode((node as ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression | NamedTupleMember).type);
case SyntaxKind.RestType:
return getTypeFromRestTypeNode(node as RestTypeNode);
case SyntaxKind.JSDocVariadicType:
return getTypeFromJSDocVariadicType(node as JSDocVariadicType);
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.TypeLiteral:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.JSDocSignature:
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node as TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature);
case SyntaxKind.TypeOperator:
return getTypeFromTypeOperatorNode(node as TypeOperatorNode);
case SyntaxKind.IndexedAccessType:
return getTypeFromIndexedAccessTypeNode(node as IndexedAccessTypeNode);
case SyntaxKind.MappedType:
return getTypeFromMappedTypeNode(node as MappedTypeNode);
case SyntaxKind.ConditionalType:
return getTypeFromConditionalTypeNode(node as ConditionalTypeNode);
case SyntaxKind.InferType:
return getTypeFromInferTypeNode(node as InferTypeNode);
case SyntaxKind.TemplateLiteralType:
return getTypeFromTemplateTypeNode(node as TemplateLiteralTypeNode);
case SyntaxKind.ImportType:
return getTypeFromImportTypeNode(node as ImportTypeNode);
// This function assumes that an identifier, qualified name, or property access expression is a type expression
// Callers should first ensure this by calling `isPartOfTypeNode`
// TODO(rbuckton): These aren't valid TypeNodes, but we treat them as such because of `isPartOfTypeNode`, which returns `true` for things that aren't `TypeNode`s.
case SyntaxKind.Identifier as TypeNodeSyntaxKind:
case SyntaxKind.QualifiedName as TypeNodeSyntaxKind:
case SyntaxKind.PropertyAccessExpression as TypeNodeSyntaxKind:
const symbol = getSymbolAtLocation(node);
return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType;
default:
return errorType;
}
}
function instantiateList(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[];
function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined;
function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined {
if (items && items.length) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
const mapped = instantiator(item, mapper);
if (item !== mapped) {
const result = i === 0 ? [] : items.slice(0, i);
result.push(mapped);
for (i++; i < items.length; i++) {
result.push(instantiator(items[i], mapper));
}
return result;
}
}
}
return items;
}
function instantiateTypes(types: readonly Type[], mapper: TypeMapper): readonly Type[];
function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined;
function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined {
return instantiateList(types, mapper, instantiateType);
}
function instantiateSignatures(signatures: readonly Signature[], mapper: TypeMapper): readonly Signature[] {
return instantiateList(signatures, mapper, instantiateSignature);
}
function instantiateIndexInfos(indexInfos: readonly IndexInfo[], mapper: TypeMapper): readonly IndexInfo[] {
return instantiateList(indexInfos, mapper, instantiateIndexInfo);
}
function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper {
return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : makeArrayTypeMapper(sources, targets);
}
function getMappedType(type: Type, mapper: TypeMapper): Type {
switch (mapper.kind) {
case TypeMapKind.Simple:
return type === mapper.source ? mapper.target : type;
case TypeMapKind.Array: {
const sources = mapper.sources;
const targets = mapper.targets;
for (let i = 0; i < sources.length; i++) {
if (type === sources[i]) {
return targets ? targets[i] : anyType;
}
}
return type;
}
case TypeMapKind.Deferred: {
const sources = mapper.sources;
const targets = mapper.targets;
for (let i = 0; i < sources.length; i++) {
if (type === sources[i]) {
return targets[i]();
}
}
return type;
}
case TypeMapKind.Function:
return mapper.func(type);
case TypeMapKind.Composite:
case TypeMapKind.Merged:
const t1 = getMappedType(type, mapper.mapper1);
return t1 !== type && mapper.kind === TypeMapKind.Composite ? instantiateType(t1, mapper.mapper2) : getMappedType(t1, mapper.mapper2);
}
}
function makeUnaryTypeMapper(source: Type, target: Type): TypeMapper {
return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Simple, source, target });
}
function makeArrayTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper {
return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Array, sources, targets });
}
function makeFunctionTypeMapper(func: (t: Type) => Type, debugInfo: () => string): TypeMapper {
return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Function, func, debugInfo: Debug.isDebugging ? debugInfo : undefined });
}
function makeDeferredTypeMapper(sources: readonly TypeParameter[], targets: (() => Type)[]) {
return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Deferred, sources, targets });
}
function makeCompositeTypeMapper(kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper {
return Debug.attachDebugPrototypeIfDebug({ kind, mapper1, mapper2 });
}
function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper {
return createTypeMapper(sources, /*targets*/ undefined);
}
/**
* Maps forward-references to later types parameters to the empty object type.
* This is used during inference when instantiating type parameter defaults.
*/
function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper {
const forwardInferences = context.inferences.slice(index);
return createTypeMapper(map(forwardInferences, i => i.typeParameter), map(forwardInferences, () => unknownType));
}
/**
* Return a type mapper that combines the context's return mapper with a mapper that erases any additional type parameters
* to their inferences at the time of creation.
*/
function createOuterReturnMapper(context: InferenceContext) {
return context.outerReturnMapper ??= mergeTypeMappers(context.returnMapper, cloneInferenceContext(context).mapper);
}
function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper {
return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2;
}
function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper {
return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2;
}
function prependTypeMapping(source: Type, target: Type, mapper: TypeMapper | undefined) {
return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, makeUnaryTypeMapper(source, target), mapper);
}
function appendTypeMapping(mapper: TypeMapper | undefined, source: Type, target: Type) {
return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, mapper, makeUnaryTypeMapper(source, target));
}
function getRestrictiveTypeParameter(tp: TypeParameter) {
return !tp.constraint && !getConstraintDeclaration(tp) || tp.constraint === noConstraintType ? tp : tp.restrictiveInstantiation || (
tp.restrictiveInstantiation = createTypeParameter(tp.symbol), (tp.restrictiveInstantiation as TypeParameter).constraint = noConstraintType, tp.restrictiveInstantiation
);
}
function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter {
const result = createTypeParameter(typeParameter.symbol);
result.target = typeParameter;
return result;
}
function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate {
return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper));
}
function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature {
let freshTypeParameters: TypeParameter[] | undefined;
if (signature.typeParameters && !eraseTypeParameters) {
// First create a fresh set of type parameters, then include a mapping from the old to the
// new type parameters in the mapper function. Finally store this mapper in the new type
// parameters such that we can use it when instantiating constraints.
freshTypeParameters = map(signature.typeParameters, cloneTypeParameter);
mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper);
for (const tp of freshTypeParameters) {
tp.mapper = mapper;
}
}
// Don't compute resolvedReturnType and resolvedTypePredicate now,
// because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.)
// See GH#17600.
const result = createSignature(signature.declaration, freshTypeParameters, signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), instantiateList(signature.parameters, mapper, instantiateSymbol), /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, signature.minArgumentCount, signature.flags & SignatureFlags.PropagatingFlags);
result.target = signature;
result.mapper = mapper;
return result;
}
function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol {
const links = getSymbolLinks(symbol);
// If the type of the symbol is already resolved, and if that type could not possibly
// be affected by instantiation, simply return the symbol itself.
if (links.type && !couldContainTypeVariables(links.type)) {
if (!(symbol.flags & SymbolFlags.SetAccessor)) {
return symbol;
}
// If we're a setter, check writeType.
if (links.writeType && !couldContainTypeVariables(links.writeType)) {
return symbol;
}
}
if (getCheckFlags(symbol) & CheckFlags.Instantiated) {
// If symbol being instantiated is itself a instantiation, fetch the original target and combine the
// type mappers. This ensures that original type identities are properly preserved and that aliases
// always reference a non-aliases.
symbol = links.target!;
mapper = combineTypeMappers(links.mapper, mapper);
}
// Keep the flags from the symbol we're instantiating. Mark that is instantiated, and
// also transient so that we can just store data on it directly.
const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter));
result.declarations = symbol.declarations;
result.parent = symbol.parent;
result.links.target = symbol;
result.links.mapper = mapper;
if (symbol.valueDeclaration) {
result.valueDeclaration = symbol.valueDeclaration;
}
if (links.nameType) {
result.links.nameType = links.nameType;
}
return result;
}
function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) {
const declaration = type.objectFlags & ObjectFlags.Reference ? (type as TypeReference).node! :
type.objectFlags & ObjectFlags.InstantiationExpressionType ? (type as InstantiationExpressionType).node :
type.symbol.declarations![0];
const links = getNodeLinks(declaration);
const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! as DeferredTypeReference :
type.objectFlags & ObjectFlags.Instantiated ? type.target! : type;
let typeParameters = links.outerTypeParameters;
if (!typeParameters) {
// The first time an anonymous type is instantiated we compute and store a list of the type
// parameters that are in scope (and therefore potentially referenced). For type literals that
// aren't the right hand side of a generic type alias declaration we optimize by reducing the
// set of type parameters to those that are possibly referenced in the literal.
let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true);
if (isJSConstructor(declaration)) {
const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters);
outerTypeParameters = addRange(outerTypeParameters, templateTagParameters);
}
typeParameters = outerTypeParameters || emptyArray;
const allDeclarations = type.objectFlags & (ObjectFlags.Reference | ObjectFlags.InstantiationExpressionType) ? [declaration] : type.symbol.declarations!;
typeParameters = (target.objectFlags & (ObjectFlags.Reference | ObjectFlags.InstantiationExpressionType) || target.symbol.flags & SymbolFlags.Method || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ?
filter(typeParameters, tp => some(allDeclarations, d => isTypeParameterPossiblyReferenced(tp, d))) :
typeParameters;
links.outerTypeParameters = typeParameters;
}
if (typeParameters.length) {
// We are instantiating an anonymous type that has one or more type parameters in scope. Apply the
// mapper to the type parameters to produce the effective list of type arguments, and compute the
// instantiation cache key from the type IDs of the type arguments.
const combinedMapper = combineTypeMappers(type.mapper, mapper);
const typeArguments = map(typeParameters, t => getMappedType(t, combinedMapper));
const newAliasSymbol = aliasSymbol || type.aliasSymbol;
const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper);
const id = getTypeListId(typeArguments) + getAliasId(newAliasSymbol, newAliasTypeArguments);
if (!target.instantiations) {
target.instantiations = new Map();
target.instantiations.set(getTypeListId(typeParameters) + getAliasId(target.aliasSymbol, target.aliasTypeArguments), target);
}
let result = target.instantiations.get(id);
if (!result) {
let newMapper = createTypeMapper(typeParameters, typeArguments);
if (target.objectFlags & ObjectFlags.SingleSignatureType && mapper) {
newMapper = combineTypeMappers(newMapper, mapper);
}
result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((type as DeferredTypeReference).target, (type as DeferredTypeReference).node, newMapper, newAliasSymbol, newAliasTypeArguments) :
target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(target as MappedType, newMapper, newAliasSymbol, newAliasTypeArguments) :
instantiateAnonymousType(target, newMapper, newAliasSymbol, newAliasTypeArguments);
target.instantiations.set(id, result); // Set cached result early in case we recursively invoke instantiation while eagerly computing type variable visibility below
const resultObjectFlags = getObjectFlags(result);
if (result.flags & TypeFlags.ObjectFlagsType && !(resultObjectFlags & ObjectFlags.CouldContainTypeVariablesComputed)) {
const resultCouldContainTypeVariables = some(typeArguments, couldContainTypeVariables); // one of the input type arguments might be or contain the result
if (!(getObjectFlags(result) & ObjectFlags.CouldContainTypeVariablesComputed)) {
// if `result` is one of the object types we tried to make (it may not be, due to how `instantiateMappedType` works), we can carry forward the type variable containment check from the input type arguments
if (resultObjectFlags & (ObjectFlags.Mapped | ObjectFlags.Anonymous | ObjectFlags.Reference)) {
(result as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariables : 0);
}
// If none of the type arguments for the outer type parameters contain type variables, it follows
// that the instantiated type doesn't reference type variables.
// Intrinsics have `CouldContainTypeVariablesComputed` pre-set, so this should only cover unions and intersections resulting from `instantiateMappedType`
else {
(result as ObjectFlagsType).objectFlags |= !resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariablesComputed : 0;
}
}
}
}
return result;
}
return type;
}
function maybeTypeParameterReference(node: Node) {
return !(node.parent.kind === SyntaxKind.TypeReference && (node.parent as TypeReferenceNode).typeArguments && node === (node.parent as TypeReferenceNode).typeName ||
node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier);
}
function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) {
// If the type parameter doesn't have exactly one declaration, if there are intervening statement blocks
// between the node and the type parameter declaration, if the node contains actual references to the
// type parameter, or if the node contains type queries that we can't prove couldn't contain references to the type parameter,
// we consider the type parameter possibly referenced.
if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) {
const container = tp.symbol.declarations[0].parent;
for (let n = node; n !== container; n = n.parent) {
if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n as ConditionalTypeNode).extendsType, containsReference)) {
return true;
}
}
return containsReference(node);
}
return true;
function containsReference(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.ThisType:
return !!tp.isThisType;
case SyntaxKind.Identifier:
return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) &&
getTypeFromTypeNodeWorker(node as TypeNode) === tp; // use worker because we're looking for === equality
case SyntaxKind.TypeQuery:
const entityName = (node as TypeQueryNode).exprName;
const firstIdentifier = getFirstIdentifier(entityName);
if (!isThisIdentifier(firstIdentifier)) { // Don't attempt to analyze typeof this.xxx
const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier);
const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called
const tpScope = tpDeclaration.kind === SyntaxKind.TypeParameter ? tpDeclaration.parent : // Type parameter is a regular type parameter, e.g. foo
tp.isThisType ? tpDeclaration : // Type parameter is the this type, and its declaration is the class declaration.
undefined; // Type parameter's declaration was unrecognized, e.g. comes from JSDoc annotation.
if (firstIdentifierSymbol.declarations && tpScope) {
return some(firstIdentifierSymbol.declarations, idDecl => isNodeDescendantOf(idDecl, tpScope)) ||
some((node as TypeQueryNode).typeArguments, containsReference);
}
}
return true;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
return !(node as FunctionLikeDeclaration).type && !!(node as FunctionLikeDeclaration).body ||
some((node as FunctionLikeDeclaration).typeParameters, containsReference) ||
some((node as FunctionLikeDeclaration).parameters, containsReference) ||
!!(node as FunctionLikeDeclaration).type && containsReference((node as FunctionLikeDeclaration).type!);
}
return !!forEachChild(node, containsReference);
}
}
function getHomomorphicTypeVariable(type: MappedType) {
const constraintType = getConstraintTypeFromMappedType(type);
if (constraintType.flags & TypeFlags.Index) {
const typeVariable = getActualTypeVariable((constraintType as IndexType).type);
if (typeVariable.flags & TypeFlags.TypeParameter) {
return typeVariable as TypeParameter;
}
}
return undefined;
}
function instantiateMappedType(type: MappedType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
// For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping
// operation depends on T as follows:
// * If T is a primitive type no mapping is performed and the result is simply T.
// * If T is a union type we distribute the mapped type over the union.
// * If T is an array we map to an array where the element type has been transformed.
// * If T is a tuple we map to a tuple where the element types have been transformed.
// * If T is an intersection of array or tuple types we map to an intersection of transformed array or tuple types.
// * Otherwise we map to an object type where the type of each property has been transformed.
// For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } |
// { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce
// { [P in keyof A]: X } | undefined.
const typeVariable = getHomomorphicTypeVariable(type);
if (typeVariable) {
const mappedTypeVariable = instantiateType(typeVariable, mapper);
if (typeVariable !== mappedTypeVariable) {
return mapTypeWithAlias(getReducedType(mappedTypeVariable), instantiateConstituent, aliasSymbol, aliasTypeArguments);
}
}
// If the constraint type of the instantiation is the wildcard type, return the wildcard type.
return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments);
function instantiateConstituent(t: Type): Type {
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) {
if (!type.declaration.nameType) {
let constraint;
if (
isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable!, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 &&
(constraint = getConstraintOfTypeParameter(typeVariable!)) && everyType(constraint, isArrayOrTupleType)
) {
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable!, t, mapper));
}
if (isTupleType(t)) {
return instantiateMappedTupleType(t, type, typeVariable!, mapper);
}
if (isArrayOrTupleOrIntersection(t)) {
return getIntersectionType(map((t as IntersectionType).types, instantiateConstituent));
}
}
return instantiateAnonymousType(type, prependTypeMapping(typeVariable!, t, mapper));
}
return t;
}
}
function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) {
return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state;
}
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) {
// We apply the mapped type's template type to each of the fixed part elements. For variadic elements, we
// apply the mapped type itself to the variadic element type. For other elements in the variable part of the
// tuple, we surround the element type with an array type and apply the mapped type to that. This ensures
// that we get sequential property key types for the fixed part of the tuple, and property key type number
// for the remaining elements. For example
//
// type Keys = { [K in keyof T]: K };
// type Foo = Keys<[string, string, ...T, string]>; // ["0", "1", ...Keys, number]
//
const elementFlags = tupleType.target.elementFlags;
const fixedLength = tupleType.target.fixedLength;
const fixedMapper = fixedLength ? prependTypeMapping(typeVariable, tupleType, mapper) : mapper;
const newElementTypes = map(getElementTypes(tupleType), (type, i) => {
const flags = elementFlags[i];
return i < fixedLength ? instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(flags & ElementFlags.Optional), fixedMapper) :
flags & ElementFlags.Variadic ? instantiateType(mappedType, prependTypeMapping(typeVariable, type, mapper)) :
getElementTypeOfArrayType(instantiateType(mappedType, prependTypeMapping(typeVariable, createArrayType(type), mapper))) ?? unknownType;
});
const modifiers = getMappedTypeModifiers(mappedType);
const newElementFlags = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) :
modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) :
elementFlags;
const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, getMappedTypeModifiers(mappedType));
return contains(newElementTypes, errorType) ? errorType :
createTupleType(newElementTypes, newElementFlags, newReadonly, tupleType.target.labeledElementDeclarations);
}
function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) {
const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper);
return isErrorType(elementType) ? errorType :
createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType)));
}
function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) {
const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key);
const propType = instantiateType(getTemplateTypeFromMappedType(type.target as MappedType || type), templateMapper);
const modifiers = getMappedTypeModifiers(type);
return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) :
strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) :
propType;
}
function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): AnonymousType {
Debug.assert(type.symbol, "anonymous type must have symbol to be instantiated");
const result = createObjectType(type.objectFlags & ~(ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.CouldContainTypeVariables) | ObjectFlags.Instantiated, type.symbol) as AnonymousType;
if (type.objectFlags & ObjectFlags.Mapped) {
(result as MappedType).declaration = (type as MappedType).declaration;
// C.f. instantiateSignature
const origTypeParameter = getTypeParameterFromMappedType(type as MappedType);
const freshTypeParameter = cloneTypeParameter(origTypeParameter);
(result as MappedType).typeParameter = freshTypeParameter;
mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper);
freshTypeParameter.mapper = mapper;
}
if (type.objectFlags & ObjectFlags.InstantiationExpressionType) {
(result as InstantiationExpressionType).node = (type as InstantiationExpressionType).node;
}
result.target = type;
result.mapper = mapper;
result.aliasSymbol = aliasSymbol || type.aliasSymbol;
result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper);
result.objectFlags |= result.aliasTypeArguments ? getPropagatingFlagsOfTypes(result.aliasTypeArguments) : 0;
return result;
}
function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
const root = type.root;
if (root.outerTypeParameters) {
// We are instantiating a conditional type that has one or more type parameters in scope. Apply the
// mapper to the type parameters to produce the effective list of type arguments, and compute the
// instantiation cache key from the type IDs of the type arguments.
const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper));
const id = (forConstraint ? "C" : "") + getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments);
let result = root.instantiations!.get(id);
if (!result) {
const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments);
const checkType = root.checkType;
const distributionType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined;
// Distributive conditional types are distributed over union types. For example, when the
// distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the
// result is (A extends U ? X : Y) | (B extends U ? X : Y).
result = distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never) ?
mapTypeWithAlias(distributionType, t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), aliasSymbol, aliasTypeArguments) :
getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments);
root.instantiations!.set(id, result);
}
return result;
}
return type;
}
function instantiateType(type: Type, mapper: TypeMapper | undefined): Type;
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined;
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined {
return type && mapper ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type;
}
function instantiateTypeWithAlias(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type {
if (!couldContainTypeVariables(type)) {
return type;
}
if (instantiationDepth === 100 || instantiationCount >= 5000000) {
// We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement
// or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types
// that perpetually generate new type identities, so we stop the recursion here by yielding the error type.
tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount });
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
return errorType;
}
const index = findActiveMapper(mapper);
if (index === -1) {
pushActiveMapper(mapper);
}
const key = type.id + getAliasId(aliasSymbol, aliasTypeArguments);
const mapperCache = activeTypeMappersCaches[index !== -1 ? index : activeTypeMappersCount - 1];
const cached = mapperCache.get(key);
if (cached) {
return cached;
}
totalInstantiationCount++;
instantiationCount++;
instantiationDepth++;
const result = instantiateTypeWorker(type, mapper, aliasSymbol, aliasTypeArguments);
if (index === -1) {
popActiveMapper();
}
else {
mapperCache.set(key, result);
}
instantiationDepth--;
return result;
}
function instantiateTypeWorker(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type {
const flags = type.flags;
if (flags & TypeFlags.TypeParameter) {
return getMappedType(type, mapper);
}
if (flags & TypeFlags.Object) {
const objectFlags = (type as ObjectType).objectFlags;
if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) {
if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) {
const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments;
const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper);
return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type;
}
if (objectFlags & ObjectFlags.ReverseMapped) {
return instantiateReverseMappedType(type as ReverseMappedType, mapper);
}
return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments);
}
return type;
}
if (flags & TypeFlags.UnionOrIntersection) {
const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined;
const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types;
const newTypes = instantiateTypes(types, mapper);
if (newTypes === types && aliasSymbol === type.aliasSymbol) {
return type;
}
const newAliasSymbol = aliasSymbol || type.aliasSymbol;
const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper);
return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ?
getIntersectionType(newTypes, IntersectionFlags.None, newAliasSymbol, newAliasTypeArguments) :
getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments);
}
if (flags & TypeFlags.Index) {
return getIndexType(instantiateType((type as IndexType).type, mapper));
}
if (flags & TypeFlags.TemplateLiteral) {
return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper));
}
if (flags & TypeFlags.StringMapping) {
return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper));
}
if (flags & TypeFlags.IndexedAccess) {
const newAliasSymbol = aliasSymbol || type.aliasSymbol;
const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper);
return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments);
}
if (flags & TypeFlags.Conditional) {
return getConditionalTypeInstantiation(
type as ConditionalType,
combineTypeMappers((type as ConditionalType).mapper, mapper),
/*forConstraint*/ false,
aliasSymbol,
aliasTypeArguments,
);
}
if (flags & TypeFlags.Substitution) {
const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper);
if (isNoInferType(type)) {
return getNoInferType(newBaseType);
}
const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper);
// A substitution type originates in the true branch of a conditional type and can be resolved
// to just the base type in the same cases as the conditional type resolves to its true branch
// (because the base type is then known to satisfy the constraint).
if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) {
return getSubstitutionType(newBaseType, newConstraint);
}
if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) {
return newBaseType;
}
return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]);
}
return type;
}
function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) {
const innerMappedType = instantiateType(type.mappedType, mapper);
if (!(getObjectFlags(innerMappedType) & ObjectFlags.Mapped)) {
return type;
}
const innerIndexType = instantiateType(type.constraintType, mapper);
if (!(innerIndexType.flags & TypeFlags.Index)) {
return type;
}
const instantiated = inferTypeForHomomorphicMappedType(
instantiateType(type.source, mapper),
innerMappedType as MappedType,
innerIndexType as IndexType,
);
if (instantiated) {
return instantiated;
}
return type; // Nested invocation of `inferTypeForHomomorphicMappedType` or the `source` instantiated into something unmappable
}
function getPermissiveInstantiation(type: Type) {
return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type :
type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper));
}
function getRestrictiveInstantiation(type: Type) {
if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) {
return type;
}
if (type.restrictiveInstantiation) {
return type.restrictiveInstantiation;
}
type.restrictiveInstantiation = instantiateType(type, restrictiveMapper);
// We set the following so we don't attempt to set the restrictive instance of a restrictive instance
// which is redundant - we'll produce new type identities, but all type params have already been mapped.
// This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint"
// assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters
// are constrained to `unknown` and produce tons of false positives/negatives!
type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation;
return type.restrictiveInstantiation;
}
function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) {
return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration, info.components);
}
// Returns true if the given expression contains (at any level of nesting) a function or arrow expression
// that is subject to contextual typing.
function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean {
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
switch (node.kind) {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type
return isContextSensitiveFunctionLikeDeclaration(node as FunctionExpression | ArrowFunction | MethodDeclaration);
case SyntaxKind.ObjectLiteralExpression:
return some((node as ObjectLiteralExpression).properties, isContextSensitive);
case SyntaxKind.ArrayLiteralExpression:
return some((node as ArrayLiteralExpression).elements, isContextSensitive);
case SyntaxKind.ConditionalExpression:
return isContextSensitive((node as ConditionalExpression).whenTrue) ||
isContextSensitive((node as ConditionalExpression).whenFalse);
case SyntaxKind.BinaryExpression:
return ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken) &&
(isContextSensitive((node as BinaryExpression).left) || isContextSensitive((node as BinaryExpression).right));
case SyntaxKind.PropertyAssignment:
return isContextSensitive((node as PropertyAssignment).initializer);
case SyntaxKind.ParenthesizedExpression:
return isContextSensitive((node as ParenthesizedExpression).expression);
case SyntaxKind.JsxAttributes:
return some((node as JsxAttributes).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive);
case SyntaxKind.JsxAttribute: {
// If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive.
const { initializer } = node as JsxAttribute;
return !!initializer && isContextSensitive(initializer);
}
case SyntaxKind.JsxExpression: {
// It is possible to that node.expression is undefined (e.g )
const { expression } = node as JsxExpression;
return !!expression && isContextSensitive(expression);
}
}
return false;
}
function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node);
}
function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) {
if (node.typeParameters || getEffectiveReturnTypeNode(node) || !node.body) {
return false;
}
if (node.body.kind !== SyntaxKind.Block) {
return isContextSensitive(node.body);
}
return !!forEachReturnStatement(node.body as Block, statement => !!statement.expression && isContextSensitive(statement.expression));
}
function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
isContextSensitiveFunctionLikeDeclaration(func);
}
function getTypeWithoutSignatures(type: Type): Type {
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(type as ObjectType);
if (resolved.constructSignatures.length || resolved.callSignatures.length) {
const result = createObjectType(ObjectFlags.Anonymous, type.symbol);
result.members = resolved.members;
result.properties = resolved.properties;
result.callSignatures = emptyArray;
result.constructSignatures = emptyArray;
result.indexInfos = emptyArray;
return result;
}
}
else if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(map((type as IntersectionType).types, getTypeWithoutSignatures));
}
return type;
}
// TYPE CHECKING
function isTypeIdenticalTo(source: Type, target: Type): boolean {
return isTypeRelatedTo(source, target, identityRelation);
}
function compareTypesIdentical(source: Type, target: Type): Ternary {
return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False;
}
function compareTypesAssignable(source: Type, target: Type): Ternary {
return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False;
}
function compareTypesSubtypeOf(source: Type, target: Type): Ternary {
return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False;
}
function isTypeSubtypeOf(source: Type, target: Type): boolean {
return isTypeRelatedTo(source, target, subtypeRelation);
}
function isTypeStrictSubtypeOf(source: Type, target: Type): boolean {
return isTypeRelatedTo(source, target, strictSubtypeRelation);
}
function isTypeAssignableTo(source: Type, target: Type): boolean {
return isTypeRelatedTo(source, target, assignableRelation);
}
// An object type S is considered to be derived from an object type T if
// S is a union type and every constituent of S is derived from T,
// T is a union type and S is derived from at least one constituent of T, or
// S is an intersection type and some constituent of S is derived from T, or
// S is a type variable with a base constraint that is derived from T, or
// T is {} and S is an object-like type (ensuring {} is less derived than Object), or
// T is one of the global types Object and Function and S is a subtype of T, or
// T occurs directly or indirectly in an 'extends' clause of S.
// Note that this check ignores type parameters and only considers the
// inheritance hierarchy.
function isTypeDerivedFrom(source: Type, target: Type): boolean {
return source.flags & TypeFlags.Union ? every((source as UnionType).types, t => isTypeDerivedFrom(t, target)) :
target.flags & TypeFlags.Union ? some((target as UnionType).types, t => isTypeDerivedFrom(source, t)) :
source.flags & TypeFlags.Intersection ? some((source as IntersectionType).types, t => isTypeDerivedFrom(t, target)) :
source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) :
isEmptyAnonymousObjectType(target) ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) :
target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) && !isEmptyAnonymousObjectType(source) :
target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) :
hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType));
}
/**
* This is *not* a bi-directional relationship.
* If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'.
*
* A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T.
* It is used to check following cases:
* - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`).
* - the types of `case` clause expressions and their respective `switch` expressions.
* - the type of an expression in a type assertion with the type being asserted.
*/
function isTypeComparableTo(source: Type, target: Type): boolean {
return isTypeRelatedTo(source, target, comparableRelation);
}
function areTypesComparable(type1: Type, type2: Type): boolean {
return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1);
}
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { errors?: Diagnostic[]; }): boolean {
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject);
}
/**
* Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to
* attempt to issue more specific errors on, for example, specific object literal properties or tuple members.
*/
function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined);
}
function checkTypeRelatedToAndOptionallyElaborate(
source: Type,
target: Type,
relation: Map,
errorNode: Node | undefined,
expr: Expression | undefined,
headMessage: DiagnosticMessage | undefined,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: ErrorOutputContainer | undefined,
): boolean {
if (isTypeRelatedTo(source, target, relation)) return true;
if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) {
return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer);
}
return false;
}
function isOrHasGenericConditional(type: Type): boolean {
return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional)));
}
function elaborateError(
node: Expression | undefined,
source: Type,
target: Type,
relation: Map,
headMessage: DiagnosticMessage | undefined,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: ErrorOutputContainer | undefined,
): boolean {
if (!node || isOrHasGenericConditional(target)) return false;
if (
!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined)
&& elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)
) {
return true;
}
switch (node.kind) {
case SyntaxKind.AsExpression:
if (!isConstAssertion(node)) {
break;
}
// fallthrough
case SyntaxKind.JsxExpression:
case SyntaxKind.ParenthesizedExpression:
return elaborateError((node as AsExpression | ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer);
case SyntaxKind.BinaryExpression:
switch ((node as BinaryExpression).operatorToken.kind) {
case SyntaxKind.EqualsToken:
case SyntaxKind.CommaToken:
return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer);
}
break;
case SyntaxKind.ObjectLiteralExpression:
return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer);
case SyntaxKind.ArrayLiteralExpression:
return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer);
case SyntaxKind.JsxAttributes:
return elaborateJsxComponents(node as JsxAttributes, source, target, relation, containingMessageChain, errorOutputContainer);
case SyntaxKind.ArrowFunction:
return elaborateArrowFunction(node as ArrowFunction, source, target, relation, containingMessageChain, errorOutputContainer);
}
return false;
}
function elaborateDidYouMeanToCallOrConstruct(
node: Expression,
source: Type,
target: Type,
relation: Map,
headMessage: DiagnosticMessage | undefined,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: ErrorOutputContainer | undefined,
): boolean {
const callSignatures = getSignaturesOfType(source, SignatureKind.Call);
const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct);
for (const signatures of [constructSignatures, callSignatures]) {
if (
some(signatures, s => {
const returnType = getReturnTypeOfSignature(s);
return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined);
})
) {
const resultObj: { errors?: Diagnostic[]; } = errorOutputContainer || {};
checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj);
const diagnostic = resultObj.errors![resultObj.errors!.length - 1];
addRelatedInfo(
diagnostic,
createDiagnosticForNode(
node,
signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression,
),
);
return true;
}
}
return false;
}
function elaborateArrowFunction(
node: ArrowFunction,
source: Type,
target: Type,
relation: Map,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: ErrorOutputContainer | undefined,
): boolean {
// Don't elaborate blocks
if (isBlock(node.body)) {
return false;
}
// Or functions with annotated parameter types
if (some(node.parameters, hasType)) {
return false;
}
const sourceSig = getSingleCallSignature(source);
if (!sourceSig) {
return false;
}
const targetSignatures = getSignaturesOfType(target, SignatureKind.Call);
if (!length(targetSignatures)) {
return false;
}
const returnExpression = node.body;
const sourceReturn = getReturnTypeOfSignature(sourceSig);
const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature));
if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) {
const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer);
if (elaborated) {
return elaborated;
}
const resultObj: { errors?: Diagnostic[]; } = errorOutputContainer || {};
checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*headMessage*/ undefined, containingMessageChain, resultObj);
if (resultObj.errors) {
if (target.symbol && length(target.symbol.declarations)) {
addRelatedInfo(
resultObj.errors[resultObj.errors.length - 1],
createDiagnosticForNode(
target.symbol.declarations![0],
Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature,
),
);
}
if (
(getFunctionFlags(node) & FunctionFlags.Async) === 0
// exclude cases where source itself is promisy - this way we don't make a suggestion when relating
// an IPromise and a Promise that are slightly different
&& !getTypeOfPropertyOfType(sourceReturn, "then" as __String)
&& checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined)
) {
addRelatedInfo(
resultObj.errors[resultObj.errors.length - 1],
createDiagnosticForNode(
node,
Diagnostics.Did_you_mean_to_mark_this_function_as_async,
),
);
}
return true;
}
}
return false;
}
function getBestMatchIndexedAccessTypeOrUndefined(source: Type, target: Type, nameType: Type) {
const idx = getIndexedAccessTypeOrUndefined(target, nameType);
if (idx) {
return idx;
}
if (target.flags & TypeFlags.Union) {
const best = getBestMatchingType(source, target as UnionType);
if (best) {
return getIndexedAccessTypeOrUndefined(best, nameType);
}
}
}
function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) {
pushContextualType(next, sourcePropType, /*isCache*/ false);
const result = checkExpressionForMutableLocation(next, CheckMode.Contextual);
popContextualType();
return result;
}
type ElaborationIterator = IterableIterator<{ errorNode: Node; innerExpression: Expression | undefined; nameType: Type; errorMessage?: DiagnosticMessage | undefined; }>;
/**
* For every element returned from the iterator, checks that element to issue an error on a property of that element's type
* If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError`
* Otherwise, we issue an error on _every_ element which fail the assignability check
*/
function elaborateElementwise(
iterator: ElaborationIterator,
source: Type,
target: Type,
relation: Map,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: ErrorOutputContainer | undefined,
) {
// Assignability failure - check each prop individually, and if that fails, fall back on the bad error span
let reportedError = false;
for (const value of iterator) {
const { errorNode: prop, innerExpression: next, nameType, errorMessage } = value;
let targetPropType = getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType);
if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables
let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType);
if (!sourcePropType) continue;
const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined);
if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) {
const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer);
reportedError = true;
if (!elaborated) {
// Issue error on the prop itself, since the prop couldn't elaborate the error
const resultObj: { errors?: Diagnostic[]; } = errorOutputContainer || {};
// Use the expression type, if available
const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType;
if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) {
const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType));
diagnostics.add(diag);
resultObj.errors = [diag];
}
else {
const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional);
const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional);
targetPropType = removeMissingType(targetPropType, targetIsOptional);
sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional);
const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
if (result && specificSource !== sourcePropType) {
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
}
}
if (resultObj.errors) {
const reportedDiag = resultObj.errors[resultObj.errors.length - 1];
const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined;
let issuedElaboration = false;
if (!targetProp) {
const indexInfo = getApplicableIndexInfo(target, nameType);
if (indexInfo && indexInfo.declaration && !getSourceFileOfNode(indexInfo.declaration).hasNoDefaultLib) {
issuedElaboration = true;
addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature));
}
}
if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) {
const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations![0] : target.symbol.declarations![0];
if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) {
addRelatedInfo(
reportedDiag,
createDiagnosticForNode(
targetNode,
Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1,
propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType),
typeToString(target),
),
);
}
}
}
}
}
}
return reportedError;
}
/**
* Assumes `target` type is assignable to the `Iterable` type, if `Iterable` is defined,
* or that it's an array or tuple-like type, if `Iterable` is not defined.
*/
function elaborateIterableOrArrayLikeTargetElementwise(
iterator: ElaborationIterator,
source: Type,
target: Type,
relation: Map,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: ErrorOutputContainer | undefined,
) {
const tupleOrArrayLikeTargetParts = filterType(target, isArrayOrTupleLikeType);
const nonTupleOrArrayLikeTargetParts = filterType(target, t => !isArrayOrTupleLikeType(t));
// If `nonTupleOrArrayLikeTargetParts` is not `never`, then that should mean `Iterable` is defined.
const iterationType = nonTupleOrArrayLikeTargetParts !== neverType
? getIterationTypeOfIterable(IterationUse.ForOf, IterationTypeKind.Yield, nonTupleOrArrayLikeTargetParts, /*errorNode*/ undefined)
: undefined;
let reportedError = false;
for (let status = iterator.next(); !status.done; status = iterator.next()) {
const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value;
let targetPropType = iterationType;
const targetIndexedPropType = tupleOrArrayLikeTargetParts !== neverType ? getBestMatchIndexedAccessTypeOrUndefined(source, tupleOrArrayLikeTargetParts, nameType) : undefined;
if (targetIndexedPropType && !(targetIndexedPropType.flags & TypeFlags.IndexedAccess)) { // Don't elaborate on indexes on generic variables
targetPropType = iterationType ? getUnionType([iterationType, targetIndexedPropType]) : targetIndexedPropType;
}
if (!targetPropType) continue;
let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType);
if (!sourcePropType) continue;
const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined);
if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) {
const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer);
reportedError = true;
if (!elaborated) {
// Issue error on the prop itself, since the prop couldn't elaborate the error
const resultObj: { errors?: Diagnostic[]; } = errorOutputContainer || {};
// Use the expression type, if available
const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType;
if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) {
const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType));
diagnostics.add(diag);
resultObj.errors = [diag];
}
else {
const targetIsOptional = !!(propName && (getPropertyOfType(tupleOrArrayLikeTargetParts, propName) || unknownSymbol).flags & SymbolFlags.Optional);
const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional);
targetPropType = removeMissingType(targetPropType, targetIsOptional);
sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional);
const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
if (result && specificSource !== sourcePropType) {
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
}
}
}
}
}
return reportedError;
}
function* generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
if (!length(node.properties)) return;
for (const prop of node.properties) {
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue;
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) };
}
}
function* generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator {
if (!length(node.children)) return;
let memberOffset = 0;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const nameType = getNumberLiteralType(i - memberOffset);
const elem = getElaborationElementForJsxChild(child, nameType, getInvalidTextDiagnostic);
if (elem) {
yield elem;
}
else {
memberOffset++;
}
}
}
function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType, getInvalidTextDiagnostic: () => DiagnosticMessage) {
switch (child.kind) {
case SyntaxKind.JsxExpression:
// child is of the type of the expression
return { errorNode: child, innerExpression: child.expression, nameType };
case SyntaxKind.JsxText:
if (child.containsOnlyTriviaWhiteSpaces) {
break; // Whitespace only jsx text isn't real jsx text
}
// child is a string
return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() };
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxFragment:
// child is of type JSX.Element
return { errorNode: child, innerExpression: child, nameType };
default:
return Debug.assertNever(child, "Found invalid jsx child");
}
}
function elaborateJsxComponents(
node: JsxAttributes,
source: Type,
target: Type,
relation: Map,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: ErrorOutputContainer | undefined,
) {
let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer);
let invalidTextDiagnostic: DiagnosticMessage | undefined;
if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) {
const containingElement = node.parent.parent;
const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName);
const childrenNameType = getStringLiteralType(childrenPropName);
const childrenTargetType = getIndexedAccessType(target, childrenNameType);
const validChildren = getSemanticJsxChildren(containingElement.children);
if (!length(validChildren)) {
return result;
}
const moreThanOneRealChildren = length(validChildren) > 1;
let arrayLikeTargetParts: Type;
let nonArrayLikeTargetParts: Type;
const iterableType = getGlobalIterableType(/*reportErrors*/ false);
if (iterableType !== emptyGenericType) {
const anyIterable = createIterableType(anyType);
arrayLikeTargetParts = filterType(childrenTargetType, t => isTypeAssignableTo(t, anyIterable));
nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isTypeAssignableTo(t, anyIterable));
}
else {
arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType);
nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t));
}
if (moreThanOneRealChildren) {
if (arrayLikeTargetParts !== neverType) {
const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal));
const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic);
result = elaborateIterableOrArrayLikeTargetElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result;
}
else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) {
// arity mismatch
result = true;
const diag = error(
containingElement.openingElement.tagName,
Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided,
childrenPropName,
typeToString(childrenTargetType),
);
if (errorOutputContainer && errorOutputContainer.skipLogging) {
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
}
}
}
else {
if (nonArrayLikeTargetParts !== neverType) {
const child = validChildren[0];
const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic);
if (elem) {
result = elaborateElementwise(
(function* () {
yield elem;
})(),
source,
target,
relation,
containingMessageChain,
errorOutputContainer,
) || result;
}
}
else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) {
// arity mismatch
result = true;
const diag = error(
containingElement.openingElement.tagName,
Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided,
childrenPropName,
typeToString(childrenTargetType),
);
if (errorOutputContainer && errorOutputContainer.skipLogging) {
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
}
}
}
}
return result;
function getInvalidTextualChildDiagnostic() {
if (!invalidTextDiagnostic) {
const tagNameText = getTextOfNode(node.parent.tagName);
const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName);
const childrenTargetType = getIndexedAccessType(target, getStringLiteralType(childrenPropName));
const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2;
invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) };
}
return invalidTextDiagnostic;
}
}
function* generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator {
const len = length(node.elements);
if (!len) return;
for (let i = 0; i < len; i++) {
// Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature
if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue;
const elem = node.elements[i];
if (isOmittedExpression(elem)) continue;
const nameType = getNumberLiteralType(i);
const checkNode = getEffectiveCheckNode(elem);
yield { errorNode: checkNode, innerExpression: checkNode, nameType };
}
}
function elaborateArrayLiteral(
node: ArrayLiteralExpression,
source: Type,
target: Type,
relation: Map,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: ErrorOutputContainer | undefined,
) {
if (target.flags & (TypeFlags.Primitive | TypeFlags.Never)) return false;
if (isTupleLikeType(source)) {
return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer);
}
// recreate a tuple from the elements, if possible
// Since we're re-doing the expression type, we need to reapply the contextual type
pushContextualType(node, target, /*isCache*/ false);
const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true);
popContextualType();
if (isTupleLikeType(tupleizedType)) {
return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer);
}
return false;
}
function* generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator {
if (!length(node.properties)) return;
for (const prop of node.properties) {
if (isSpreadAssignment(prop)) continue;
const type = getLiteralTypeFromProperty(getSymbolOfDeclaration(prop), TypeFlags.StringOrNumberLiteralOrUnique);
if (!type || (type.flags & TypeFlags.Never)) {
continue;
}
switch (prop.kind) {
case SyntaxKind.SetAccessor:
case SyntaxKind.GetAccessor:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.ShorthandPropertyAssignment:
yield { errorNode: prop.name, innerExpression: undefined, nameType: type };
break;
case SyntaxKind.PropertyAssignment:
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined };
break;
default:
Debug.assertNever(prop);
}
}
}
function elaborateObjectLiteral(
node: ObjectLiteralExpression,
source: Type,
target: Type,
relation: Map,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: ErrorOutputContainer | undefined,
) {
if (target.flags & (TypeFlags.Primitive | TypeFlags.Never)) return false;
return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer);
}
/**
* This is *not* a bi-directional relationship.
* If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'.
*/
function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain);
}
function isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean {
return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : SignatureCheckMode.None, /*reportErrors*/ false, /*errorReporter*/ undefined, /*incompatibleErrorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False;
}
type ErrorReporter = (message: DiagnosticMessage, ...args: DiagnosticArguments) => void;
/**
* Returns true if `s` is `(...args: A) => R` where `A` is `any`, `any[]`, `never`, or `never[]`, and `R` is `any` or `unknown`.
*/
function isTopSignature(s: Signature) {
if (!s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && signatureHasRestParameter(s)) {
const paramType = getTypeOfParameter(s.parameters[0]);
const restType = isArrayType(paramType) ? getTypeArguments(paramType)[0] : paramType;
return !!(restType.flags & (TypeFlags.Any | TypeFlags.Never) && getReturnTypeOfSignature(s).flags & TypeFlags.AnyOrUnknown);
}
return false;
}
/**
* See signatureRelatedTo, compareSignaturesIdentical
*/
function compareSignaturesRelated(source: Signature, target: Signature, checkMode: SignatureCheckMode, reportErrors: boolean, errorReporter: ErrorReporter | undefined, incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined, compareTypes: TypeComparer, reportUnreliableMarkers: TypeMapper | undefined): Ternary {
// TODO (drosen): De-duplicate code between related functions.
if (source === target) {
return Ternary.True;
}
if (!(checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source)) && isTopSignature(target)) {
return Ternary.True;
}
if (checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source) && !isTopSignature(target)) {
return Ternary.False;
}
const targetCount = getParameterCount(target);
const sourceHasMoreParameters = !hasEffectiveRestParameter(target) &&
(checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount);
if (sourceHasMoreParameters) {
if (reportErrors && !(checkMode & SignatureCheckMode.StrictArity)) {
// the second condition should be redundant, because there is no error reporting when comparing signatures by strict arity
// since it is only done for subtype reduction
errorReporter!(Diagnostics.Target_signature_provides_too_few_arguments_Expected_0_or_more_but_got_1, getMinArgumentCount(source), targetCount);
}
return Ternary.False;
}
if (source.typeParameters && source.typeParameters !== target.typeParameters) {
target = getCanonicalSignature(target);
source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes);
}
const sourceCount = getParameterCount(source);
const sourceRestType = getNonArrayRestType(source);
const targetRestType = getNonArrayRestType(target);
if (sourceRestType || targetRestType) {
void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers);
}
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor;
let result = Ternary.True;
const sourceThisType = getThisTypeOfSignature(source);
if (sourceThisType && sourceThisType !== voidType) {
const targetThisType = getThisTypeOfSignature(target);
if (targetThisType) {
// void sources are assignable to anything.
const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false)
|| compareTypes(targetThisType, sourceThisType, reportErrors);
if (!related) {
if (reportErrors) {
errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible);
}
return Ternary.False;
}
result &= related;
}
}
const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount);
const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1;
for (let i = 0; i < paramCount; i++) {
const sourceType = i === restIndex ? getRestOrAnyTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i);
const targetType = i === restIndex ? getRestOrAnyTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i);
if (sourceType && targetType && (sourceType !== targetType || checkMode & SignatureCheckMode.StrictArity)) {
// In order to ensure that any generic type Foo is at least co-variant with respect to T no matter
// how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions,
// they naturally relate only contra-variantly). However, if the source and target parameters both have
// function types with a single call signature, we know we are relating two callback parameters. In
// that case it is sufficient to only relate the parameters of the signatures co-variantly because,
// similar to return values, callback parameters are output positions. This means that a Promise,
// where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant)
// with respect to T.
const sourceSig = checkMode & SignatureCheckMode.Callback || isInstantiatedGenericParameter(source, i) ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
const targetSig = checkMode & SignatureCheckMode.Callback || isInstantiatedGenericParameter(target, i) ? undefined : getSingleCallSignature(getNonNullableType(targetType));
const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) &&
getTypeFacts(sourceType, TypeFacts.IsUndefinedOrNull) === getTypeFacts(targetType, TypeFacts.IsUndefinedOrNull);
let related = callbacks ?
compareSignaturesRelated(targetSig, sourceSig, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
!(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
// With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void
if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) {
related = Ternary.False;
}
if (!related) {
if (reportErrors) {
errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), unescapeLeadingUnderscores(getParameterNameAtPosition(target, i)));
}
return Ternary.False;
}
result &= related;
}
}
if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) {
// If a signature resolution is already in-flight, skip issuing a circularity error
// here and just use the `any` type directly
const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType
: target.declaration && isJSConstructor(target.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(target.declaration.symbol))
: getReturnTypeOfSignature(target);
if (targetReturnType === voidType || targetReturnType === anyType) {
return result;
}
const sourceReturnType = isResolvingReturnTypeOfSignature(source) ? anyType
: source.declaration && isJSConstructor(source.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(source.declaration.symbol))
: getReturnTypeOfSignature(source);
// The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions
const targetTypePredicate = getTypePredicateOfSignature(target);
if (targetTypePredicate) {
const sourceTypePredicate = getTypePredicateOfSignature(source);
if (sourceTypePredicate) {
result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes);
}
else if (isIdentifierTypePredicate(targetTypePredicate) || isThisTypePredicate(targetTypePredicate)) {
if (reportErrors) {
errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source));
}
return Ternary.False;
}
}
else {
// When relating callback signatures, we still need to relate return types bi-variantly as otherwise
// the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void }
// wouldn't be co-variant for T without this rule.
result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
compareTypes(sourceReturnType, targetReturnType, reportErrors);
if (!result && reportErrors && incompatibleErrorReporter) {
incompatibleErrorReporter(sourceReturnType, targetReturnType);
}
}
}
return result;
}
function compareTypePredicateRelatedTo(
source: TypePredicate,
target: TypePredicate,
reportErrors: boolean,
errorReporter: ErrorReporter | undefined,
compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary,
): Ternary {
if (source.kind !== target.kind) {
if (reportErrors) {
errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard);
errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target));
}
return Ternary.False;
}
if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) {
if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) {
if (reportErrors) {
errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName);
errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target));
}
return Ternary.False;
}
}
const related = source.type === target.type ? Ternary.True :
source.type && target.type ? compareTypes(source.type, target.type, reportErrors) :
Ternary.False;
if (related === Ternary.False && reportErrors) {
errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target));
}
return related;
}
function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean {
const erasedSource = getErasedSignature(implementation);
const erasedTarget = getErasedSignature(overload);
// First see if the return types are compatible in either direction.
const sourceReturnType = getReturnTypeOfSignature(erasedSource);
const targetReturnType = getReturnTypeOfSignature(erasedTarget);
if (
targetReturnType === voidType
|| isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation)
|| isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)
) {
return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true);
}
return false;
}
function isEmptyResolvedType(t: ResolvedType) {
return t !== anyFunctionType &&
t.properties.length === 0 &&
t.callSignatures.length === 0 &&
t.constructSignatures.length === 0 &&
t.indexInfos.length === 0;
}
function isEmptyObjectType(type: Type): boolean {
return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(type as ObjectType)) :
type.flags & TypeFlags.NonPrimitive ? true :
type.flags & TypeFlags.Union ? some((type as UnionType).types, isEmptyObjectType) :
type.flags & TypeFlags.Intersection ? every((type as UnionType).types, isEmptyObjectType) :
false;
}
function isEmptyAnonymousObjectType(type: Type) {
return !!(getObjectFlags(type) & ObjectFlags.Anonymous && (
(type as ResolvedType).members && isEmptyResolvedType(type as ResolvedType) ||
type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && getMembersOfSymbol(type.symbol).size === 0
));
}
function isUnknownLikeUnionType(type: Type) {
if (strictNullChecks && type.flags & TypeFlags.Union) {
if (!((type as UnionType).objectFlags & ObjectFlags.IsUnknownLikeUnionComputed)) {
const types = (type as UnionType).types;
(type as UnionType).objectFlags |= ObjectFlags.IsUnknownLikeUnionComputed | (types.length >= 3 && types[0].flags & TypeFlags.Undefined &&
types[1].flags & TypeFlags.Null && some(types, isEmptyAnonymousObjectType) ? ObjectFlags.IsUnknownLikeUnion : 0);
}
return !!((type as UnionType).objectFlags & ObjectFlags.IsUnknownLikeUnion);
}
return false;
}
function containsUndefinedType(type: Type) {
return !!((type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type).flags & TypeFlags.Undefined);
}
function containsNonMissingUndefinedType(type: Type) {
const candidate = type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type;
return !!(candidate.flags & TypeFlags.Undefined) && candidate !== missingType;
}
function isStringIndexSignatureOnlyType(type: Type): boolean {
return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, stringType) ||
type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isStringIndexSignatureOnlyType) ||
false;
}
function isEnumTypeRelatedTo(source: Symbol, target: Symbol, errorReporter?: ErrorReporter) {
const sourceSymbol = source.flags & SymbolFlags.EnumMember ? getParentOfSymbol(source)! : source;
const targetSymbol = target.flags & SymbolFlags.EnumMember ? getParentOfSymbol(target)! : target;
if (sourceSymbol === targetSymbol) {
return true;
}
if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) {
return false;
}
const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol);
const entry = enumRelation.get(id);
if (entry !== undefined && !(entry & RelationComparisonResult.Failed && errorReporter)) {
return !!(entry & RelationComparisonResult.Succeeded);
}
const targetEnumType = getTypeOfSymbol(targetSymbol);
for (const sourceProperty of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) {
if (sourceProperty.flags & SymbolFlags.EnumMember) {
const targetProperty = getPropertyOfType(targetEnumType, sourceProperty.escapedName);
if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) {
if (errorReporter) {
errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(sourceProperty), typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType));
}
enumRelation.set(id, RelationComparisonResult.Failed);
return false;
}
const sourceValue = getEnumMemberValue(getDeclarationOfKind(sourceProperty, SyntaxKind.EnumMember)!).value;
const targetValue = getEnumMemberValue(getDeclarationOfKind(targetProperty, SyntaxKind.EnumMember)!).value;
if (sourceValue !== targetValue) {
const sourceIsString = typeof sourceValue === "string";
const targetIsString = typeof targetValue === "string";
// If we have 2 enums with *known* values that differ, they are incompatible.
if (sourceValue !== undefined && targetValue !== undefined) {
if (errorReporter) {
const escapedSource = sourceIsString ? `"${escapeString(sourceValue)}"` : sourceValue;
const escapedTarget = targetIsString ? `"${escapeString(targetValue)}"` : targetValue;
errorReporter(Diagnostics.Each_declaration_of_0_1_differs_in_its_value_where_2_was_expected_but_3_was_given, symbolName(targetSymbol), symbolName(targetProperty), escapedTarget, escapedSource);
}
enumRelation.set(id, RelationComparisonResult.Failed);
return false;
}
// At this point we know that at least one of the values is 'undefined'.
// This may mean that we have an opaque member from an ambient enum declaration,
// or that we were not able to calculate it (which is basically an error).
//
// Either way, we can assume that it's numeric.
// If the other is a string, we have a mismatch in types.
if (sourceIsString || targetIsString) {
if (errorReporter) {
const knownStringValue = sourceValue ?? targetValue;
Debug.assert(typeof knownStringValue === "string");
const escapedValue = `"${escapeString(knownStringValue)}"`;
errorReporter(Diagnostics.One_value_of_0_1_is_the_string_2_and_the_other_is_assumed_to_be_an_unknown_numeric_value, symbolName(targetSymbol), symbolName(targetProperty), escapedValue);
}
enumRelation.set(id, RelationComparisonResult.Failed);
return false;
}
}
}
}
enumRelation.set(id, RelationComparisonResult.Succeeded);
return true;
}
function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) {
const s = source.flags;
const t = target.flags;
if (t & TypeFlags.Any || s & TypeFlags.Never || source === wildcardType) return true;
if (t & TypeFlags.Unknown && !(relation === strictSubtypeRelation && s & TypeFlags.Any)) return true;
if (t & TypeFlags.Never) return false;
if (s & TypeFlags.StringLike && t & TypeFlags.String) return true;
if (
s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral &&
t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) &&
(source as StringLiteralType).value === (target as StringLiteralType).value
) return true;
if (s & TypeFlags.NumberLike && t & TypeFlags.Number) return true;
if (
s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral &&
t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) &&
(source as NumberLiteralType).value === (target as NumberLiteralType).value
) return true;
if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true;
if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true;
if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true;
if (
s & TypeFlags.Enum && t & TypeFlags.Enum && source.symbol.escapedName === target.symbol.escapedName &&
isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)
) return true;
if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) {
if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true;
if (
s & TypeFlags.Literal && t & TypeFlags.Literal && (source as LiteralType).value === (target as LiteralType).value &&
isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)
) return true;
}
// In non-strictNullChecks mode, `undefined` and `null` are assignable to anything except `never`.
// Since unions and intersections may reduce to `never`, we exclude them here.
if (s & TypeFlags.Undefined && (!strictNullChecks && !(t & TypeFlags.UnionOrIntersection) || t & (TypeFlags.Undefined | TypeFlags.Void))) return true;
if (s & TypeFlags.Null && (!strictNullChecks && !(t & TypeFlags.UnionOrIntersection) || t & TypeFlags.Null)) return true;
if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive && !(relation === strictSubtypeRelation && isEmptyAnonymousObjectType(source) && !(getObjectFlags(source) & ObjectFlags.FreshLiteral))) return true;
if (relation === assignableRelation || relation === comparableRelation) {
if (s & TypeFlags.Any) return true;
// Type number is assignable to any computed numeric enum type or any numeric enum literal type, and
// a numeric literal type is assignable any computed numeric enum type or any numeric enum literal type
// with a matching value. These rules exist such that enums can be used for bit-flag purposes.
if (s & TypeFlags.Number && (t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true;
if (
s & TypeFlags.NumberLiteral && !(s & TypeFlags.EnumLiteral) && (t & TypeFlags.Enum ||
t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral &&
(source as NumberLiteralType).value === (target as NumberLiteralType).value)
) return true;
// Anything is assignable to a union containing undefined, null, and {}
if (isUnknownLikeUnionType(target)) return true;
}
return false;
}
function isTypeRelatedTo(source: Type, target: Type, relation: Map) {
if (isFreshLiteralType(source)) {
source = (source as FreshableType).regularType;
}
if (isFreshLiteralType(target)) {
target = (target as FreshableType).regularType;
}
if (source === target) {
return true;
}
if (relation !== identityRelation) {
if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) {
return true;
}
}
else if (!((source.flags | target.flags) & (TypeFlags.UnionOrIntersection | TypeFlags.IndexedAccess | TypeFlags.Conditional | TypeFlags.Substitution))) {
// We have excluded types that may simplify to other forms, so types must have identical flags
if (source.flags !== target.flags) return false;
if (source.flags & TypeFlags.Singleton) return true;
}
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false));
if (related !== undefined) {
return !!(related & RelationComparisonResult.Succeeded);
}
}
if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined);
}
return false;
}
function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) {
return getObjectFlags(source) & ObjectFlags.JsxAttributes && isHyphenatedJsxName(sourceProp.escapedName);
}
function getNormalizedType(type: Type, writing: boolean): Type {
while (true) {
const t = isFreshLiteralType(type) ? (type as FreshableType).regularType :
isGenericTupleType(type) ? getNormalizedTupleType(type, writing) :
getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).node ? createTypeReference((type as TypeReference).target, getTypeArguments(type as TypeReference)) : getSingleBaseForNonAugmentingSubtype(type) || type :
type.flags & TypeFlags.UnionOrIntersection ? getNormalizedUnionOrIntersectionType(type as UnionOrIntersectionType, writing) :
type.flags & TypeFlags.Substitution ? writing ? (type as SubstitutionType).baseType : getSubstitutionIntersection(type as SubstitutionType) :
type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) :
type;
if (t === type) return t;
type = t;
}
}
function getNormalizedUnionOrIntersectionType(type: UnionOrIntersectionType, writing: boolean) {
const reduced = getReducedType(type);
if (reduced !== type) {
return reduced;
}
if (type.flags & TypeFlags.Intersection && shouldNormalizeIntersection(type as IntersectionType)) {
// Normalization handles cases like
// Partial[K] & ({} | null) ==>
// Partial[K] & {} | Partial[K] & null ==>
// (T[K] | undefined) & {} | (T[K] | undefined) & null ==>
// T[K] & {} | undefined & {} | T[K] & null | undefined & null ==>
// T[K] & {} | T[K] & null
const normalizedTypes = sameMap(type.types, t => getNormalizedType(t, writing));
if (normalizedTypes !== type.types) {
return getIntersectionType(normalizedTypes);
}
}
return type;
}
function shouldNormalizeIntersection(type: IntersectionType) {
let hasInstantiable = false;
let hasNullableOrEmpty = false;
for (const t of type.types) {
hasInstantiable ||= !!(t.flags & TypeFlags.Instantiable);
hasNullableOrEmpty ||= !!(t.flags & TypeFlags.Nullable) || isEmptyAnonymousObjectType(t);
if (hasInstantiable && hasNullableOrEmpty) return true;
}
return false;
}
function getNormalizedTupleType(type: TupleTypeReference, writing: boolean): Type {
const elements = getElementTypes(type);
const normalizedElements = sameMap(elements, t => t.flags & TypeFlags.Simplifiable ? getSimplifiedType(t, writing) : t);
return elements !== normalizedElements ? createNormalizedTupleType(type.target, normalizedElements) : type;
}
/**
* Checks if 'source' is related to 'target' (e.g.: is a assignable to).
* @param source The left-hand-side of the relation.
* @param target The right-hand-side of the relation.
* @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'.
* Used as both to determine which checks are performed and as a cache of previously computed results.
* @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used.
* @param headMessage If the error chain should be prepended by a head message, then headMessage will be used.
* @param containingMessageChain A chain of errors to prepend any new errors found.
* @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy.
*/
function checkTypeRelatedTo(
source: Type,
target: Type,
relation: Map,
errorNode: Node | undefined,
headMessage?: DiagnosticMessage,
containingMessageChain?: () => DiagnosticMessageChain | undefined,
errorOutputContainer?: ErrorOutputContainer,
): boolean {
let errorInfo: DiagnosticMessageChain | undefined;
let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined;
let maybeKeys: string[];
let maybeKeysSet: Set;
let sourceStack: Type[];
let targetStack: Type[];
let maybeCount = 0;
let sourceDepth = 0;
let targetDepth = 0;
let expandingFlags = ExpandingFlags.None;
let overflow = false;
let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid
let skipParentCounter = 0; // How many errors should be skipped 'above' in the elaboration pyramid
let lastSkippedInfo: [Type, Type] | undefined;
let incompatibleStack: DiagnosticAndArguments[] | undefined;
// In Node.js, the maximum number of elements in a map is 2^24. We limit the number of entries an invocation
// of checkTypeRelatedTo can add to a relation to 1/8th of its remaining capacity.
let relationCount = (16_000_000 - relation.size) >> 3;
Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking");
const result = isRelatedTo(source, target, RecursionFlags.Both, /*reportErrors*/ !!errorNode, headMessage);
if (incompatibleStack) {
reportIncompatibleStack();
}
if (overflow) {
// Record this relation as having failed such that we don't attempt the overflowing operation again.
const id = getRelationKey(source, target, /*intersectionState*/ IntersectionState.None, relation, /*ignoreConstraints*/ false);
relation.set(id, RelationComparisonResult.Failed | (relationCount <= 0 ? RelationComparisonResult.ComplexityOverflow : RelationComparisonResult.StackDepthOverflow));
tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth });
const message = relationCount <= 0 ?
Diagnostics.Excessive_complexity_comparing_types_0_and_1 :
Diagnostics.Excessive_stack_depth_comparing_types_0_and_1;
const diag = error(errorNode || currentNode, message, typeToString(source), typeToString(target));
if (errorOutputContainer) {
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
}
}
else if (errorInfo) {
if (containingMessageChain) {
const chain = containingMessageChain();
if (chain) {
concatenateDiagnosticMessageChains(chain, errorInfo);
errorInfo = chain;
}
}
let relatedInformation: DiagnosticRelatedInformation[] | undefined;
// Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement
if (headMessage && errorNode && !result && source.symbol) {
const links = getSymbolLinks(source.symbol);
if (links.originatingImport && !isImportCall(links.originatingImport)) {
const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined);
if (helpfulRetry) {
// Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import
const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead);
relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it
}
}
}
const diag = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(errorNode!), errorNode!, errorInfo, relatedInformation);
if (relatedInfo) {
addRelatedInfo(diag, ...relatedInfo);
}
if (errorOutputContainer) {
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
}
if (!errorOutputContainer || !errorOutputContainer.skipLogging) {
diagnostics.add(diag);
}
}
if (errorNode && errorOutputContainer && errorOutputContainer.skipLogging && result === Ternary.False) {
Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error.");
}
return result !== Ternary.False;
function resetErrorInfo(saved: ReturnType) {
errorInfo = saved.errorInfo;
lastSkippedInfo = saved.lastSkippedInfo;
incompatibleStack = saved.incompatibleStack;
overrideNextErrorInfo = saved.overrideNextErrorInfo;
skipParentCounter = saved.skipParentCounter;
relatedInfo = saved.relatedInfo;
}
function captureErrorCalculationState() {
return {
errorInfo,
lastSkippedInfo,
incompatibleStack: incompatibleStack?.slice(),
overrideNextErrorInfo,
skipParentCounter,
relatedInfo: relatedInfo?.slice() as [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined,
};
}
function reportIncompatibleError(message: DiagnosticMessage, ...args: DiagnosticArguments) {
overrideNextErrorInfo++; // Suppress the next relation error
lastSkippedInfo = undefined; // Reset skipped info cache
(incompatibleStack ||= []).push([message, ...args]);
}
function reportIncompatibleStack() {
const stack = incompatibleStack || [];
incompatibleStack = undefined;
const info = lastSkippedInfo;
lastSkippedInfo = undefined;
if (stack.length === 1) {
reportError(...stack[0]);
if (info) {
// Actually do the last relation error
reportRelationError(/*message*/ undefined, ...info);
}
return;
}
// The first error will be the innermost, while the last will be the outermost - so by popping off the end,
// we can build from left to right
let path = "";
const secondaryRootErrors: DiagnosticAndArguments[] = [];
while (stack.length) {
const [msg, ...args] = stack.pop()!;
switch (msg.code) {
case Diagnostics.Types_of_property_0_are_incompatible.code: {
// Parenthesize a `new` if there is one
if (path.indexOf("new ") === 0) {
path = `(${path})`;
}
const str = "" + args[0];
// If leading, just print back the arg (irrespective of if it's a valid identifier)
if (path.length === 0) {
path = `${str}`;
}
// Otherwise write a dotted name if possible
else if (isIdentifierText(str, getEmitScriptTarget(compilerOptions))) {
path = `${path}.${str}`;
}
// Failing that, check if the name is already a computed name
else if (str[0] === "[" && str[str.length - 1] === "]") {
path = `${path}${str}`;
}
// And finally write out a computed name as a last resort
else {
path = `${path}[${str}]`;
}
break;
}
case Diagnostics.Call_signature_return_types_0_and_1_are_incompatible.code:
case Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code:
case Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code:
case Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: {
if (path.length === 0) {
// Don't flatten signature compatability errors at the start of a chain - instead prefer
// to unify (the with no arguments bit is excessive for printback) and print them back
let mappedMsg = msg;
if (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) {
mappedMsg = Diagnostics.Call_signature_return_types_0_and_1_are_incompatible;
}
else if (msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) {
mappedMsg = Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible;
}
secondaryRootErrors.unshift([mappedMsg, args[0], args[1]]);
}
else {
const prefix = (msg.code === Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code ||
msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code)
? "new "
: "";
const params = (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code ||
msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code)
? ""
: "...";
path = `${prefix}${path}(${params})`;
}
break;
}
case Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target.code: {
secondaryRootErrors.unshift([Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, args[0], args[1]]);
break;
}
case Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target.code: {
secondaryRootErrors.unshift([Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, args[0], args[1], args[2]]);
break;
}
default:
return Debug.fail(`Unhandled Diagnostic: ${msg.code}`);
}
}
if (path) {
reportError(
path[path.length - 1] === ")"
? Diagnostics.The_types_returned_by_0_are_incompatible_between_these_types
: Diagnostics.The_types_of_0_are_incompatible_between_these_types,
path,
);
}
else {
// Remove the innermost secondary error as it will duplicate the error already reported by `reportRelationError` on entry
secondaryRootErrors.shift();
}
for (const [msg, ...args] of secondaryRootErrors) {
const originalValue = msg.elidedInCompatabilityPyramid;
msg.elidedInCompatabilityPyramid = false; // Temporarily override elision to ensure error is reported
reportError(msg, ...args);
msg.elidedInCompatabilityPyramid = originalValue;
}
if (info) {
// Actually do the last relation error
reportRelationError(/*message*/ undefined, ...info);
}
}
function reportError(message: DiagnosticMessage, ...args: DiagnosticArguments): void {
Debug.assert(!!errorNode);
if (incompatibleStack) reportIncompatibleStack();
if (message.elidedInCompatabilityPyramid) return;
if (skipParentCounter === 0) {
errorInfo = chainDiagnosticMessages(errorInfo, message, ...args);
}
else {
skipParentCounter--;
}
}
function reportParentSkippedError(message: DiagnosticMessage, ...args: DiagnosticArguments): void {
reportError(message, ...args);
skipParentCounter++;
}
function associateRelatedInfo(info: DiagnosticRelatedInformation) {
Debug.assert(!!errorInfo);
if (!relatedInfo) {
relatedInfo = [info];
}
else {
relatedInfo.push(info);
}
}
function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) {
if (incompatibleStack) reportIncompatibleStack();
const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target);
let generalizedSource = source;
let generalizedSourceType = sourceType;
// Don't generalize on 'never' - we really want the original type
// to be displayed for use-cases like 'assertNever'.
if (!(target.flags & TypeFlags.Never) && isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) {
generalizedSource = getBaseTypeOfLiteralType(source);
Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable");
generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource);
}
// If `target` is of indexed access type (And `source` it is not), we use the object type of `target` for better error reporting
const targetFlags = target.flags & TypeFlags.IndexedAccess && !(source.flags & TypeFlags.IndexedAccess) ?
(target as IndexedAccessType).objectType.flags :
target.flags;
if (targetFlags & TypeFlags.TypeParameter && target !== markerSuperTypeForCheck && target !== markerSubTypeForCheck) {
const constraint = getBaseConstraintOfType(target);
let needsOriginalSource;
if (constraint && (isTypeAssignableTo(generalizedSource, constraint) || (needsOriginalSource = isTypeAssignableTo(source, constraint)))) {
reportError(
Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2,
needsOriginalSource ? sourceType : generalizedSourceType,
targetType,
typeToString(constraint),
);
}
else {
errorInfo = undefined;
reportError(
Diagnostics._0_could_be_instantiated_with_an_arbitrary_type_which_could_be_unrelated_to_1,
targetType,
generalizedSourceType,
);
}
}
if (!message) {
if (relation === comparableRelation) {
message = Diagnostics.Type_0_is_not_comparable_to_type_1;
}
else if (sourceType === targetType) {
message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated;
}
else if (exactOptionalPropertyTypes && getExactOptionalUnassignableProperties(source, target).length) {
message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties;
}
else {
if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) {
const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType);
if (suggestedType) {
reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, typeToString(suggestedType));
return;
}
}
message = Diagnostics.Type_0_is_not_assignable_to_type_1;
}
}
else if (
message === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1
&& exactOptionalPropertyTypes
&& getExactOptionalUnassignableProperties(source, target).length
) {
message = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties;
}
reportError(message, generalizedSourceType, targetType);
}
function tryElaborateErrorsForPrimitivesAndObjects(source: Type, target: Type) {
const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source);
const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target);
if (
(globalStringType === source && stringType === target) ||
(globalNumberType === source && numberType === target) ||
(globalBooleanType === source && booleanType === target) ||
(getGlobalESSymbolType() === source && esSymbolType === target)
) {
reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType);
}
}
/**
* Try and elaborate array and tuple errors. Returns false
* if we have found an elaboration, or we should ignore
* any other elaborations when relating the `source` and
* `target` types.
*/
function tryElaborateArrayLikeErrors(source: Type, target: Type, reportErrors: boolean): boolean {
/**
* The spec for elaboration is:
* - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations.
* - If the source is a tuple then skip property elaborations if the target is an array or tuple.
* - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations.
* - If the source an array then skip property elaborations if the target is a tuple.
*/
if (isTupleType(source)) {
if (source.target.readonly && isMutableArrayOrTuple(target)) {
if (reportErrors) {
reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target));
}
return false;
}
return isArrayOrTupleType(target);
}
if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) {
if (reportErrors) {
reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target));
}
return false;
}
if (isTupleType(target)) {
return isArrayType(source);
}
return true;
}
function isRelatedToWorker(source: Type, target: Type, reportErrors?: boolean) {
return isRelatedTo(source, target, RecursionFlags.Both, reportErrors);
}
/**
* Compare two types and return
* * Ternary.True if they are related with no assumptions,
* * Ternary.Maybe if they are related with assumptions of other relationships, or
* * Ternary.False if they are not related.
*/
function isRelatedTo(originalSource: Type, originalTarget: Type, recursionFlags: RecursionFlags = RecursionFlags.Both, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary {
if (originalSource === originalTarget) return Ternary.True;
// Before normalization: if `source` is type an object type, and `target` is primitive,
// skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result
if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) {
if (
relation === comparableRelation && !(originalTarget.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(originalTarget, originalSource, relation) ||
isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined)
) {
return Ternary.True;
}
if (reportErrors) {
reportErrorResults(originalSource, originalTarget, originalSource, originalTarget, headMessage);
}
return Ternary.False;
}
// Normalize the source and target types: Turn fresh literal types into regular literal types,
// turn deferred type references into regular type references, simplify indexed access and
// conditional types, and resolve substitution types to either the substitution (on the source
// side) or the type variable (on the target side).
const source = getNormalizedType(originalSource, /*writing*/ false);
let target = getNormalizedType(originalTarget, /*writing*/ true);
if (source === target) return Ternary.True;
if (relation === identityRelation) {
if (source.flags !== target.flags) return Ternary.False;
if (source.flags & TypeFlags.Singleton) return Ternary.True;
traceUnionsOrIntersectionsTooLarge(source, target);
return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None, recursionFlags);
}
// We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common,
// and otherwise, for type parameters in large unions, causes us to need to compare the union to itself,
// as we break down the _target_ union first, _then_ get the source constraint - so for every
// member of the target, we attempt to find a match in the source. This avoids that in cases where
// the target is exactly the constraint.
if (source.flags & TypeFlags.TypeParameter && getConstraintOfType(source) === target) {
return Ternary.True;
}
// See if we're relating a definitely non-nullable type to a union that includes null and/or undefined
// plus a single non-nullable type. If so, remove null and/or undefined from the target type.
if (source.flags & TypeFlags.DefinitelyNonNullable && target.flags & TypeFlags.Union) {
const types = (target as UnionType).types;
const candidate = types.length === 2 && types[0].flags & TypeFlags.Nullable ? types[1] :
types.length === 3 && types[0].flags & TypeFlags.Nullable && types[1].flags & TypeFlags.Nullable ? types[2] :
undefined;
if (candidate && !(candidate.flags & TypeFlags.Nullable)) {
target = getNormalizedType(candidate, /*writing*/ true);
if (source === target) return Ternary.True;
}
}
if (
relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) ||
isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)
) return Ternary.True;
if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral);
if (isPerformingExcessPropertyChecks) {
if (hasExcessProperties(source as FreshObjectLiteralType, target, reportErrors)) {
if (reportErrors) {
reportRelationError(headMessage, source, originalTarget.aliasSymbol ? originalTarget : target);
}
return Ternary.False;
}
}
const isPerformingCommonPropertyChecks = (relation !== comparableRelation || isUnitType(source)) &&
!(intersectionState & IntersectionState.Target) &&
source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType &&
target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) &&
(getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source));
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) {
if (reportErrors) {
const sourceString = typeToString(originalSource.aliasSymbol ? originalSource : source);
const targetString = typeToString(originalTarget.aliasSymbol ? originalTarget : target);
const calls = getSignaturesOfType(source, SignatureKind.Call);
const constructs = getSignaturesOfType(source, SignatureKind.Construct);
if (
calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, RecursionFlags.Source, /*reportErrors*/ false) ||
constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, RecursionFlags.Source, /*reportErrors*/ false)
) {
reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, sourceString, targetString);
}
else {
reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, sourceString, targetString);
}
}
return Ternary.False;
}
traceUnionsOrIntersectionsTooLarge(source, target);
const skipCaching = source.flags & TypeFlags.Union && (source as UnionType).types.length < 4 && !(target.flags & TypeFlags.Union) ||
target.flags & TypeFlags.Union && (target as UnionType).types.length < 4 && !(source.flags & TypeFlags.StructuredOrInstantiable);
const result = skipCaching ?
unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState) :
recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags);
if (result) {
return result;
}
}
if (reportErrors) {
reportErrorResults(originalSource, originalTarget, source, target, headMessage);
}
return Ternary.False;
}
function reportErrorResults(originalSource: Type, originalTarget: Type, source: Type, target: Type, headMessage: DiagnosticMessage | undefined) {
const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource);
const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget);
source = (originalSource.aliasSymbol || sourceHasBase) ? originalSource : source;
target = (originalTarget.aliasSymbol || targetHasBase) ? originalTarget : target;
let maybeSuppress = overrideNextErrorInfo > 0;
if (maybeSuppress) {
overrideNextErrorInfo--;
}
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
const currentError = errorInfo;
tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ true);
if (errorInfo !== currentError) {
maybeSuppress = !!errorInfo;
}
}
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) {
tryElaborateErrorsForPrimitivesAndObjects(source, target);
}
else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) {
reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead);
}
else if (getObjectFlags(source) & ObjectFlags.JsxAttributes && target.flags & TypeFlags.Intersection) {
const targetTypes = (target as IntersectionType).types;
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode);
const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode);
if (
!isErrorType(intrinsicAttributes) && !isErrorType(intrinsicClassAttributes) &&
(contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))
) {
// do not report top error
return;
}
}
else {
errorInfo = elaborateNeverIntersection(errorInfo, originalTarget);
}
// Used by, eg, missing property checking to replace the top-level message with a more informative one.
if (!headMessage && maybeSuppress) {
// We suppress a call to `reportRelationError` or not depending on the state of the type checker, so
// we call `reportRelationError` here and then undo its effects to figure out what would be the diagnostic
// if we hadn't supress it, and save that as a canonical diagnostic for deduplication purposes.
const savedErrorState = captureErrorCalculationState();
reportRelationError(headMessage, source, target);
let canonical;
if (errorInfo && errorInfo !== savedErrorState.errorInfo) {
canonical = { code: errorInfo.code, messageText: errorInfo.messageText };
}
resetErrorInfo(savedErrorState);
if (canonical && errorInfo) {
errorInfo.canonicalHead = canonical;
}
lastSkippedInfo = [source, target];
return;
}
reportRelationError(headMessage, source, target);
if (source.flags & TypeFlags.TypeParameter && source.symbol?.declarations?.[0] && !getConstraintOfType(source as TypeVariable)) {
const syntheticParam = cloneTypeParameter(source as TypeParameter);
syntheticParam.constraint = instantiateType(target, makeUnaryTypeMapper(source, syntheticParam));
if (hasNonCircularBaseConstraint(syntheticParam)) {
const targetConstraintString = typeToString(target, source.symbol.declarations[0]);
associateRelatedInfo(createDiagnosticForNode(source.symbol.declarations[0], Diagnostics.This_type_parameter_might_need_an_extends_0_constraint, targetConstraintString));
}
}
}
function traceUnionsOrIntersectionsTooLarge(source: Type, target: Type): void {
if (!tracing) {
return;
}
if ((source.flags & TypeFlags.UnionOrIntersection) && (target.flags & TypeFlags.UnionOrIntersection)) {
const sourceUnionOrIntersection = source as UnionOrIntersectionType;
const targetUnionOrIntersection = target as UnionOrIntersectionType;
if (sourceUnionOrIntersection.objectFlags & targetUnionOrIntersection.objectFlags & ObjectFlags.PrimitiveUnion) {
// There's a fast path for comparing primitive unions
return;
}
const sourceSize = sourceUnionOrIntersection.types.length;
const targetSize = targetUnionOrIntersection.types.length;
if (sourceSize * targetSize > 1E6) {
tracing.instant(tracing.Phase.CheckTypes, "traceUnionsOrIntersectionsTooLarge_DepthLimit", {
sourceId: source.id,
sourceSize,
targetId: target.id,
targetSize,
pos: errorNode?.pos,
end: errorNode?.end,
});
}
}
}
function getTypeOfPropertyInTypes(types: Type[], name: __String) {
const appendPropType = (propTypes: Type[] | undefined, type: Type) => {
type = getApparentType(type);
const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name) : getPropertyOfObjectType(type, name);
const propType = prop && getTypeOfSymbol(prop) || getApplicableIndexInfoForName(type, name)?.type || undefinedType;
return append(propTypes, propType);
};
return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray);
}
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) {
return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny
}
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
if (
(relation === assignableRelation || relation === comparableRelation) &&
(isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))
) {
return false;
}
let reducedTarget = target;
let checkTypes: Type[] | undefined;
if (target.flags & TypeFlags.Union) {
reducedTarget = findMatchingDiscriminantType(source, target as UnionType, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target as UnionType);
checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget as UnionType).types : [reducedTarget];
}
for (const prop of getPropertiesOfType(source)) {
if (shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop)) {
if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) {
if (reportErrors) {
// Report error in terms of object types in the target as those are the only ones
// we check in isKnownProperty.
const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget);
// We know *exactly* where things went wrong when comparing the types.
// Use this property as the error node as this will be more helpful in
// reasoning about what went wrong.
if (!errorNode) return Debug.fail();
if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) {
// JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal.
// However, using an object-literal error message will be very confusing to the users so we give different a message.
if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) {
// Note that extraneous children (as in `extra`) don't pass this check,
// since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute.
errorNode = prop.valueDeclaration.name;
}
const propName = symbolToString(prop);
const suggestionSymbol = getSuggestedSymbolForNonexistentJSXAttribute(propName, errorTarget);
const suggestion = suggestionSymbol ? symbolToString(suggestionSymbol) : undefined;
if (suggestion) {
reportError(Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, typeToString(errorTarget), suggestion);
}
else {
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, propName, typeToString(errorTarget));
}
}
else {
// use the property's value declaration if the property is assigned inside the literal itself
const objectLiteralDeclaration = source.symbol?.declarations && firstOrUndefined(source.symbol.declarations);
let suggestion: string | undefined;
if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) {
const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike;
Debug.assertNode(propDeclaration, isObjectLiteralElementLike);
const name = propDeclaration.name!;
errorNode = name;
if (isIdentifier(name)) {
suggestion = getSuggestionForNonexistentProperty(name, errorTarget);
}
}
if (suggestion !== undefined) {
reportParentSkippedError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, symbolToString(prop), typeToString(errorTarget), suggestion);
}
else {
reportParentSkippedError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, symbolToString(prop), typeToString(errorTarget));
}
}
}
return true;
}
if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), RecursionFlags.Both, reportErrors)) {
if (reportErrors) {
reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop));
}
return true;
}
}
}
return false;
}
function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) {
return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration;
}
function unionOrIntersectionRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
// Note that these checks are specifically ordered to produce correct results. In particular,
// we need to deconstruct unions before intersections (because unions are always at the top),
// and we need to handle "each" relations before "some" relations for the same kind of type.
if (source.flags & TypeFlags.Union) {
if (target.flags & TypeFlags.Union) {
// Intersections of union types are normalized into unions of intersection types, and such normalized
// unions can get very large and expensive to relate. The following fast path checks if the source union
// originated in an intersection. If so, and if that intersection contains the target type, then we know
// the result to be true (for any two types A and B, A & B is related to both A and B).
const sourceOrigin = (source as UnionType).origin;
if (sourceOrigin && sourceOrigin.flags & TypeFlags.Intersection && target.aliasSymbol && contains((sourceOrigin as IntersectionType).types, target)) {
return Ternary.True;
}
// Similarly, in unions of unions the we preserve the original list of unions. This original list is often
// much shorter than the normalized result, so we scan it in the following fast path.
const targetOrigin = (target as UnionType).origin;
if (targetOrigin && targetOrigin.flags & TypeFlags.Union && source.aliasSymbol && contains((targetOrigin as UnionType).types, source)) {
return Ternary.True;
}
}
return relation === comparableRelation ?
someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) :
eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState);
}
if (target.flags & TypeFlags.Union) {
return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive), intersectionState);
}
if (target.flags & TypeFlags.Intersection) {
return typeRelatedToEachType(source, target as IntersectionType, reportErrors, IntersectionState.Target);
}
// Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the
// constraints of all non-primitive types in the source into a new intersection. We do this because the
// intersection may further constrain the constraints of the non-primitive types. For example, given a type
// parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't
// appear to be comparable to '2'.
if (relation === comparableRelation && target.flags & TypeFlags.Primitive) {
const constraints = sameMap((source as IntersectionType).types, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(t) || unknownType : t);
if (constraints !== (source as IntersectionType).types) {
source = getIntersectionType(constraints);
if (source.flags & TypeFlags.Never) {
return Ternary.False;
}
if (!(source.flags & TypeFlags.Intersection)) {
return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false) ||
isRelatedTo(target, source, RecursionFlags.Source, /*reportErrors*/ false);
}
}
}
// Check to see if any constituents of the intersection are immediately related to the target.
// Don't report errors though. Elaborating on whether a source constituent is related to the target is
// not actually useful and leads to some confusing error messages. Instead, we rely on the caller
// checking whether the full intersection viewed as an object is related to the target.
return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source);
}
function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary {
let result = Ternary.True;
const sourceTypes = source.types;
for (const sourceType of sourceTypes) {
const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false, IntersectionState.None);
if (!related) {
return Ternary.False;
}
result &= related;
}
return result;
}
function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
const targetTypes = target.types;
if (target.flags & TypeFlags.Union) {
if (containsType(targetTypes, source)) {
return Ternary.True;
}
if (
relation !== comparableRelation && getObjectFlags(target) & ObjectFlags.PrimitiveUnion && !(source.flags & TypeFlags.EnumLiteral) && (
source.flags & (TypeFlags.StringLiteral | TypeFlags.BooleanLiteral | TypeFlags.BigIntLiteral) ||
(relation === subtypeRelation || relation === strictSubtypeRelation) && source.flags & TypeFlags.NumberLiteral
)
) {
// When relating a literal type to a union of primitive types, we know the relation is false unless
// the union contains the base primitive type or the literal type in one of its fresh/regular forms.
// We exclude numeric literals for non-subtype relations because numeric literals are assignable to
// numeric enum literals with the same value. Similarly, we exclude enum literal types because
// identically named enum types are related (see isEnumTypeRelatedTo). We exclude the comparable
// relation in entirety because it needs to be checked in both directions.
const alternateForm = source === (source as StringLiteralType).regularType ? (source as StringLiteralType).freshType : (source as StringLiteralType).regularType;
const primitive = source.flags & TypeFlags.StringLiteral ? stringType :
source.flags & TypeFlags.NumberLiteral ? numberType :
source.flags & TypeFlags.BigIntLiteral ? bigintType :
undefined;
return primitive && containsType(targetTypes, primitive) || alternateForm && containsType(targetTypes, alternateForm) ? Ternary.True : Ternary.False;
}
const match = getMatchingUnionConstituentForType(target as UnionType, source);
if (match) {
const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
if (related) {
return related;
}
}
}
for (const type of targetTypes) {
const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
if (related) {
return related;
}
}
if (reportErrors) {
// Elaborate only if we can find a best matching type in the target union
const bestMatchingType = getBestMatchingType(source, target, isRelatedTo);
if (bestMatchingType) {
isRelatedTo(source, bestMatchingType, RecursionFlags.Target, /*reportErrors*/ true, /*headMessage*/ undefined, intersectionState);
}
}
return Ternary.False;
}
function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
let result = Ternary.True;
const targetTypes = target.types;
for (const targetType of targetTypes) {
const related = isRelatedTo(source, targetType, RecursionFlags.Target, reportErrors, /*headMessage*/ undefined, intersectionState);
if (!related) {
return Ternary.False;
}
result &= related;
}
return result;
}
function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
const sourceTypes = source.types;
if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) {
return Ternary.True;
}
const len = sourceTypes.length;
for (let i = 0; i < len; i++) {
const related = isRelatedTo(sourceTypes[i], target, RecursionFlags.Source, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState);
if (related) {
return related;
}
}
return Ternary.False;
}
function getUndefinedStrippedTargetIfNeeded(source: Type, target: Type) {
if (
source.flags & TypeFlags.Union && target.flags & TypeFlags.Union &&
!((source as UnionType).types[0].flags & TypeFlags.Undefined) && (target as UnionType).types[0].flags & TypeFlags.Undefined
) {
return extractTypesOfKind(target, ~TypeFlags.Undefined);
}
return target;
}
function eachTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
let result = Ternary.True;
const sourceTypes = source.types;
// We strip `undefined` from the target if the `source` trivially doesn't contain it for our correspondence-checking fastpath
// since `undefined` is frequently added by optionality and would otherwise spoil a potentially useful correspondence
const undefinedStrippedTarget = getUndefinedStrippedTargetIfNeeded(source, target as UnionType);
for (let i = 0; i < sourceTypes.length; i++) {
const sourceType = sourceTypes[i];
if (undefinedStrippedTarget.flags & TypeFlags.Union && sourceTypes.length >= (undefinedStrippedTarget as UnionType).types.length && sourceTypes.length % (undefinedStrippedTarget as UnionType).types.length === 0) {
// many unions are mappings of one another; in such cases, simply comparing members at the same index can shortcut the comparison
// such unions will have identical lengths, and their corresponding elements will match up. Another common scenario is where a large
// union has a union of objects intersected with it. In such cases, if the input was, eg `("a" | "b" | "c") & (string | boolean | {} | {whatever})`,
// the result will have the structure `"a" | "b" | "c" | "a" & {} | "b" & {} | "c" & {} | "a" & {whatever} | "b" & {whatever} | "c" & {whatever}`
// - the resulting union has a length which is a multiple of the original union, and the elements correspond modulo the length of the original union
const related = isRelatedTo(sourceType, (undefinedStrippedTarget as UnionType).types[i % (undefinedStrippedTarget as UnionType).types.length], RecursionFlags.Both, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
if (related) {
result &= related;
continue;
}
}
const related = isRelatedTo(sourceType, target, RecursionFlags.Source, reportErrors, /*headMessage*/ undefined, intersectionState);
if (!related) {
return Ternary.False;
}
result &= related;
}
return result;
}
function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
if (sources.length !== targets.length && relation === identityRelation) {
return Ternary.False;
}
const length = sources.length <= targets.length ? sources.length : targets.length;
let result = Ternary.True;
for (let i = 0; i < length; i++) {
// When variance information isn't available we default to covariance. This happens
// in the process of computing variance information for recursive types and when
// comparing 'this' type arguments.
const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant;
const variance = varianceFlags & VarianceFlags.VarianceMask;
// We ignore arguments for independent type parameters (because they're never witnessed).
if (variance !== VarianceFlags.Independent) {
const s = sources[i];
const t = targets[i];
let related = Ternary.True;
if (varianceFlags & VarianceFlags.Unmeasurable) {
// Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_.
// We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tained by
// the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be)
related = relation === identityRelation ? isRelatedTo(s, t, RecursionFlags.Both, /*reportErrors*/ false) : compareTypesIdentical(s, t);
}
else if (variance === VarianceFlags.Covariant) {
related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
else if (variance === VarianceFlags.Contravariant) {
related = isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
else if (variance === VarianceFlags.Bivariant) {
// In the bivariant case we first compare contravariantly without reporting
// errors. Then, if that doesn't succeed, we compare covariantly with error
// reporting. Thus, error elaboration will be based on the the covariant check,
// which is generally easier to reason about.
related = isRelatedTo(t, s, RecursionFlags.Both, /*reportErrors*/ false);
if (!related) {
related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
}
else {
// In the invariant case we first compare covariantly, and only when that
// succeeds do we proceed to compare contravariantly. Thus, error elaboration
// will typically be based on the covariant check.
related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
if (related) {
related &= isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
}
if (!related) {
return Ternary.False;
}
result &= related;
}
}
return result;
}
// Determine if possibly recursive types are related. First, check if the result is already available in the global cache.
// Second, check if we have already started a comparison of the given two types in which case we assume the result to be true.
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
// equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion
// and issue an error. Otherwise, actually compare the structure of the two types.
function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, recursionFlags: RecursionFlags): Ternary {
if (overflow) {
return Ternary.False;
}
const id = getRelationKey(source, target, intersectionState, relation, /*ignoreConstraints*/ false);
const entry = relation.get(id);
if (entry !== undefined) {
if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Overflow)) {
// We are elaborating errors and the cached result is a failure not due to a comparison overflow,
// so we will do the comparison again to generate an error message.
}
else {
if (outofbandVarianceMarkerHandler) {
// We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component
const saved = entry & RelationComparisonResult.ReportsMask;
if (saved & RelationComparisonResult.ReportsUnmeasurable) {
instantiateType(source, reportUnmeasurableMapper);
}
if (saved & RelationComparisonResult.ReportsUnreliable) {
instantiateType(source, reportUnreliableMapper);
}
}
if (reportErrors && entry & RelationComparisonResult.Overflow) {
const message = entry & RelationComparisonResult.ComplexityOverflow ?
Diagnostics.Excessive_complexity_comparing_types_0_and_1 :
Diagnostics.Excessive_stack_depth_comparing_types_0_and_1;
reportError(message, typeToString(source), typeToString(target));
overrideNextErrorInfo++;
}
return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
}
}
if (relationCount <= 0) {
overflow = true;
return Ternary.False;
}
if (!maybeKeys) {
maybeKeys = [];
maybeKeysSet = new Set();
sourceStack = [];
targetStack = [];
}
else {
// If source and target are already being compared, consider them related with assumptions
if (maybeKeysSet.has(id)) {
return Ternary.Maybe;
}
// A key that starts with "*" is an indication that we have type references that reference constrained
// type parameters. For such keys we also check against the key we would have gotten if all type parameters
// were unconstrained.
const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, intersectionState, relation, /*ignoreConstraints*/ true) : undefined;
if (broadestEquivalentId && maybeKeysSet.has(broadestEquivalentId)) {
return Ternary.Maybe;
}
if (sourceDepth === 100 || targetDepth === 100) {
overflow = true;
return Ternary.False;
}
}
const maybeStart = maybeCount;
maybeKeys[maybeCount] = id;
maybeKeysSet.add(id);
maybeCount++;
const saveExpandingFlags = expandingFlags;
if (recursionFlags & RecursionFlags.Source) {
sourceStack[sourceDepth] = source;
sourceDepth++;
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source;
}
if (recursionFlags & RecursionFlags.Target) {
targetStack[targetDepth] = target;
targetDepth++;
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target;
}
let originalHandler: typeof outofbandVarianceMarkerHandler;
let propagatingVarianceFlags = 0 as RelationComparisonResult;
if (outofbandVarianceMarkerHandler) {
originalHandler = outofbandVarianceMarkerHandler;
outofbandVarianceMarkerHandler = onlyUnreliable => {
propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable;
return originalHandler!(onlyUnreliable);
};
}
let result: Ternary;
if (expandingFlags === ExpandingFlags.Both) {
tracing?.instant(tracing.Phase.CheckTypes, "recursiveTypeRelatedTo_DepthLimit", {
sourceId: source.id,
sourceIdStack: sourceStack.map(t => t.id),
targetId: target.id,
targetIdStack: targetStack.map(t => t.id),
depth: sourceDepth,
targetDepth,
});
result = Ternary.Maybe;
}
else {
tracing?.push(tracing.Phase.CheckTypes, "structuredTypeRelatedTo", { sourceId: source.id, targetId: target.id });
result = structuredTypeRelatedTo(source, target, reportErrors, intersectionState);
tracing?.pop();
}
if (outofbandVarianceMarkerHandler) {
outofbandVarianceMarkerHandler = originalHandler;
}
if (recursionFlags & RecursionFlags.Source) {
sourceDepth--;
}
if (recursionFlags & RecursionFlags.Target) {
targetDepth--;
}
expandingFlags = saveExpandingFlags;
if (result) {
if (result === Ternary.True || (sourceDepth === 0 && targetDepth === 0)) {
if (result === Ternary.True || result === Ternary.Maybe) {
// If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe
// results as having succeeded once we reach depth 0, but never record Ternary.Unknown results.
resetMaybeStack(/*markAllAsSucceeded*/ true);
}
else {
resetMaybeStack(/*markAllAsSucceeded*/ false);
}
}
// Note: it's intentional that we don't reset in the else case;
// we leave them on the stack such that when we hit depth zero
// above, we can report all of them as successful.
}
else {
// A false result goes straight into global cache (when something is false under
// assumptions it will also be false without assumptions)
relation.set(id, RelationComparisonResult.Failed | propagatingVarianceFlags);
relationCount--;
resetMaybeStack(/*markAllAsSucceeded*/ false);
}
return result;
function resetMaybeStack(markAllAsSucceeded: boolean) {
for (let i = maybeStart; i < maybeCount; i++) {
maybeKeysSet.delete(maybeKeys[i]);
if (markAllAsSucceeded) {
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
relationCount--;
}
}
maybeCount = maybeStart;
}
}
function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
const saveErrorInfo = captureErrorCalculationState();
let result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState, saveErrorInfo);
if (relation !== identityRelation) {
// The combined constraint of an intersection type is the intersection of the constraints of
// the constituents. When an intersection type contains instantiable types with union type
// constraints, there are situations where we need to examine the combined constraint. One is
// when the target is a union type. Another is when the intersection contains types belonging
// to one of the disjoint domains. For example, given type variables T and U, each with the
// constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and
// we need to check this constraint against a union on the target side. Also, given a type
// variable V constrained to 'string | number', 'V & number' has a combined constraint of
// 'string & number | number & number' which reduces to just 'number'.
// This also handles type parameters, as a type parameter with a union constraint compared against a union
// needs to have its constraint hoisted into an intersection with said type parameter, this way
// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
if (!result && (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union)) {
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types : [source], !!(target.flags & TypeFlags.Union));
if (constraint && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
}
}
// When the target is an intersection we need an extra property check in order to detect nested excess
// properties and nested weak types. The following are motivating examples that all should be errors, but
// aren't without this extra property check:
//
// let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property
//
// declare let wrong: { a: { y: string } };
// let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type
//
if (
result && !(intersectionState & IntersectionState.Target) && target.flags & TypeFlags.Intersection &&
!isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection)
) {
result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, IntersectionState.None);
if (result && isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) {
result &= indexSignaturesRelatedTo(source, target, /*sourceIsPrimitive*/ false, reportErrors, IntersectionState.None);
}
}
// When the source is an intersection we need an extra check of any optional properties in the target to
// detect possible mismatched property types. For example:
//
// function foo(x: { a?: string }, y: T & { a: boolean }) {
// x = y; // Mismatched property in source intersection
// }
//
else if (
result && isNonGenericObjectType(target) && !isArrayOrTupleType(target) &&
source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType &&
!some((source as IntersectionType).types, t => t === target || !!(getObjectFlags(t) & ObjectFlags.NonInferrableType))
) {
result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ true, intersectionState);
}
}
if (result) {
resetErrorInfo(saveErrorInfo);
}
return result;
}
function getApparentMappedTypeKeys(nameType: Type, targetType: MappedType) {
const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType));
const mappedKeys: Type[] = [];
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(
modifiersType,
TypeFlags.StringOrNumberLiteralOrUnique,
/*stringsOnly*/ false,
t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))),
);
return getUnionType(mappedKeys);
}
function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, saveErrorInfo: ReturnType): Ternary {
let result: Ternary;
let originalErrorInfo: DiagnosticMessageChain | undefined;
let varianceCheckFailed = false;
let sourceFlags = source.flags;
const targetFlags = target.flags;
if (relation === identityRelation) {
// We've already checked that source.flags and target.flags are identical
if (sourceFlags & TypeFlags.UnionOrIntersection) {
let result = eachTypeRelatedToSomeType(source as UnionOrIntersectionType, target as UnionOrIntersectionType);
if (result) {
result &= eachTypeRelatedToSomeType(target as UnionOrIntersectionType, source as UnionOrIntersectionType);
}
return result;
}
if (sourceFlags & TypeFlags.Index) {
return isRelatedTo((source as IndexType).type, (target as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false);
}
if (sourceFlags & TypeFlags.IndexedAccess) {
if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, /*reportErrors*/ false)) {
if (result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, /*reportErrors*/ false)) {
return result;
}
}
}
if (sourceFlags & TypeFlags.Conditional) {
if ((source as ConditionalType).root.isDistributive === (target as ConditionalType).root.isDistributive) {
if (result = isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both, /*reportErrors*/ false)) {
if (result &= isRelatedTo((source as ConditionalType).extendsType, (target as ConditionalType).extendsType, RecursionFlags.Both, /*reportErrors*/ false)) {
if (result &= isRelatedTo(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) {
if (result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) {
return result;
}
}
}
}
}
}
if (sourceFlags & TypeFlags.Substitution) {
if (result = isRelatedTo((source as SubstitutionType).baseType, (target as SubstitutionType).baseType, RecursionFlags.Both, /*reportErrors*/ false)) {
if (result &= isRelatedTo((source as SubstitutionType).constraint, (target as SubstitutionType).constraint, RecursionFlags.Both, /*reportErrors*/ false)) {
return result;
}
}
}
if (sourceFlags & TypeFlags.TemplateLiteral) {
if (arrayIsEqualTo((source as TemplateLiteralType).texts, (target as TemplateLiteralType).texts)) {
const sourceTypes = (source as TemplateLiteralType).types;
const targetTypes = (target as TemplateLiteralType).types;
result = Ternary.True;
for (let i = 0; i < sourceTypes.length; i++) {
if (!(result &= isRelatedTo(sourceTypes[i], targetTypes[i], RecursionFlags.Both, /*reportErrors*/ false))) {
break;
}
}
return result;
}
}
if (sourceFlags & TypeFlags.StringMapping) {
if ((source as StringMappingType).symbol === (target as StringMappingType).symbol) {
return isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, /*reportErrors*/ false);
}
}
if (!(sourceFlags & TypeFlags.Object)) {
return Ternary.False;
}
}
else if (sourceFlags & TypeFlags.UnionOrIntersection || targetFlags & TypeFlags.UnionOrIntersection) {
if (result = unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState)) {
return result;
}
// The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle:
// Source is instantiable (e.g. source has union or intersection constraint).
// Source is an object, target is a union (e.g. { a, b: boolean } <=> { a, b: true } | { a, b: false }).
// Source is an intersection, target is an object (e.g. { a } & { b } <=> { a, b }).
// Source is an intersection, target is a union (e.g. { a } & { b: boolean } <=> { a, b: true } | { a, b: false }).
// Source is an intersection, target instantiable (e.g. string & { tag } <=> T["a"] constrained to string & { tag }).
if (
!(sourceFlags & TypeFlags.Instantiable ||
sourceFlags & TypeFlags.Object && targetFlags & TypeFlags.Union ||
sourceFlags & TypeFlags.Intersection && targetFlags & (TypeFlags.Object | TypeFlags.Union | TypeFlags.Instantiable))
) {
return Ternary.False;
}
}
// We limit alias variance probing to only object and conditional types since their alias behavior
// is more predictable than other, interned types, which may or may not have an alias depending on
// the order in which things were checked.
if (
sourceFlags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && source.aliasTypeArguments &&
source.aliasSymbol === target.aliasSymbol && !(isMarkerType(source) || isMarkerType(target))
) {
const variances = getAliasVariances(source.aliasSymbol);
if (variances === emptyArray) {
return Ternary.Unknown;
}
const params = getSymbolLinks(source.aliasSymbol).typeParameters!;
const minParams = getMinTypeArgumentCount(params);
const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration));
const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration));
const varianceResult = relateVariances(sourceTypes, targetTypes, variances, intersectionState);
if (varianceResult !== undefined) {
return varianceResult;
}
}
// For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T],
// and U is assignable to [...T] when U is constrained to a mutable array or tuple type.
if (
isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target, RecursionFlags.Source)) ||
isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0], RecursionFlags.Target))
) {
return result;
}
if (targetFlags & TypeFlags.TypeParameter) {
// A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q].
if (getObjectFlags(source) & ObjectFlags.Mapped && !(source as MappedType).declaration.nameType && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(source as MappedType), RecursionFlags.Both)) {
if (!(getMappedTypeModifiers(source as MappedType) & MappedTypeModifiers.IncludeOptional)) {
const templateType = getTemplateTypeFromMappedType(source as MappedType);
const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source as MappedType));
if (result = isRelatedTo(templateType, indexedAccessType, RecursionFlags.Both, reportErrors)) {
return result;
}
}
}
if (relation === comparableRelation && sourceFlags & TypeFlags.TypeParameter) {
// This is a carve-out in comparability to essentially forbid comparing a type parameter
// with another type parameter unless one extends the other. (Remember: comparability is mostly bidirectional!)
let constraint = getConstraintOfTypeParameter(source);
if (constraint) {
while (constraint && someType(constraint, c => !!(c.flags & TypeFlags.TypeParameter))) {
if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false)) {
return result;
}
constraint = getConstraintOfTypeParameter(constraint);
}
}
return Ternary.False;
}
}
else if (targetFlags & TypeFlags.Index) {
const targetType = (target as IndexType).type;
// A keyof S is related to a keyof T if T is related to S.
if (sourceFlags & TypeFlags.Index) {
if (result = isRelatedTo(targetType, (source as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false)) {
return result;
}
}
if (isTupleType(targetType)) {
// An index type can have a tuple type target when the tuple type contains variadic elements.
// Check if the source is related to the known keys of the tuple type.
if (result = isRelatedTo(source, getKnownKeysOfTupleType(targetType), RecursionFlags.Target, reportErrors)) {
return result;
}
}
else {
// 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 constraint = getSimplifiedTypeOrConstraint(targetType);
if (constraint) {
// We require Ternary.True here such that circular constraints don't cause
// false positives. For example, given 'T extends { [K in keyof T]: string }',
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
// related to other types.
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).indexFlags | IndexFlags.NoReducibleCheck), RecursionFlags.Target, reportErrors) === Ternary.True) {
return Ternary.True;
}
}
else if (isGenericMappedType(targetType)) {
// generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against
// - their nameType or constraintType.
// In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types
const nameType = getNameTypeFromMappedType(targetType);
const constraintType = getConstraintTypeFromMappedType(targetType);
let targetKeys;
if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) {
// we need to get the apparent mappings and union them with the generic mappings, since some properties may be
// missing from the `constraintType` which will otherwise be mapped in the object
const mappedKeys = getApparentMappedTypeKeys(nameType, targetType);
// We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side)
targetKeys = getUnionType([mappedKeys, nameType]);
}
else {
targetKeys = nameType || constraintType;
}
if (isRelatedTo(source, targetKeys, RecursionFlags.Target, reportErrors) === Ternary.True) {
return Ternary.True;
}
}
}
}
else if (targetFlags & TypeFlags.IndexedAccess) {
if (sourceFlags & TypeFlags.IndexedAccess) {
// Relate components directly before falling back to constraint relationships
// A type S[K] is related to a type T[J] if S is related to T and K is related to J.
if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, reportErrors)) {
result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, reportErrors);
}
if (result) {
return result;
}
if (reportErrors) {
originalErrorInfo = errorInfo;
}
}
// 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 === assignableRelation || relation === comparableRelation) {
const objectType = (target as IndexedAccessType).objectType;
const indexType = (target as IndexedAccessType).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, accessFlags);
if (constraint) {
if (reportErrors && originalErrorInfo) {
// create a new chain for the constraint error
resetErrorInfo(saveErrorInfo);
}
if (result = isRelatedTo(source, constraint, RecursionFlags.Target, reportErrors, /*headMessage*/ undefined, intersectionState)) {
return result;
}
// prefer the shorter chain of the constraint comparison chain, and the direct comparison chain
if (reportErrors && originalErrorInfo && errorInfo) {
errorInfo = countMessageChainBreadth([originalErrorInfo]) <= countMessageChainBreadth([errorInfo]) ? originalErrorInfo : errorInfo;
}
}
}
}
if (reportErrors) {
originalErrorInfo = undefined;
}
}
else if (isGenericMappedType(target) && relation !== identityRelation) {
// 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 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)) {
// 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, IndexFlags.NoIndexSignatures);
const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional;
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(targetKeys, sourceKeys, RecursionFlags.Both)
) {
const templateType = getTemplateTypeFromMappedType(target);
const typeParameter = getTypeParameterFromMappedType(target);
// 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 (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) {
if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, RecursionFlags.Target, reportErrors)) {
return result;
}
}
else {
// 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, RecursionFlags.Both, reportErrors)) {
return result;
}
}
}
originalErrorInfo = errorInfo;
resetErrorInfo(saveErrorInfo);
}
}
}
else if (targetFlags & TypeFlags.Conditional) {
// If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive
// conditional type and bail out with a Ternary.Maybe result.
if (isDeeplyNestedType(target, targetStack, targetDepth, 10)) {
return Ternary.Maybe;
}
const c = target as ConditionalType;
// We check for a relationship to a conditional type target only when the conditional type has no
// 'infer' positions, is not distributive or is distributive but doesn't reference the check type
// parameter in either of the result types, and the source isn't an instantiation of the same
// conditional type (as happens when computing variance).
if (!c.root.inferTypeParameters && !isDistributionDependent(c.root) && !(source.flags & TypeFlags.Conditional && (source as ConditionalType).root === c.root)) {
// Check if the conditional is always true or always false but still deferred for distribution purposes.
const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType));
const skipFalse = !skipTrue && isTypeAssignableTo(getRestrictiveInstantiation(c.checkType), getRestrictiveInstantiation(c.extendsType));
// TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't)
if (result = skipTrue ? Ternary.True : isRelatedTo(source, getTrueTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
result &= skipFalse ? Ternary.True : isRelatedTo(source, getFalseTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
if (result) {
return result;
}
}
}
}
else if (targetFlags & TypeFlags.TemplateLiteral) {
if (sourceFlags & TypeFlags.TemplateLiteral) {
if (relation === comparableRelation) {
return templateLiteralTypesDefinitelyUnrelated(source as TemplateLiteralType, target as TemplateLiteralType) ? Ternary.False : Ternary.True;
}
// Report unreliable variance for type variables referenced in template literal type placeholders.
// For example, `foo-${number}` is related to `foo-${string}` even though number isn't related to string.
instantiateType(source, reportUnreliableMapper);
}
if (isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)) {
return Ternary.True;
}
}
else if (target.flags & TypeFlags.StringMapping) {
if (!(source.flags & TypeFlags.StringMapping)) {
if (isMemberOfStringMapping(source, target)) {
return Ternary.True;
}
}
}
if (sourceFlags & TypeFlags.TypeVariable) {
// IndexedAccess comparisons are handled above in the `targetFlags & TypeFlage.IndexedAccess` branch
if (!(sourceFlags & TypeFlags.IndexedAccess && targetFlags & TypeFlags.IndexedAccess)) {
const constraint = getConstraintOfType(source as TypeVariable) || unknownType;
// 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
if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
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, RecursionFlags.Source, reportErrors && constraint !== unknownType && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) {
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)) {
return result;
}
}
}
}
}
else if (sourceFlags & TypeFlags.Index) {
const isDeferredMappedIndex = shouldDeferIndexType((source as IndexType).type, (source as IndexType).indexFlags) && getObjectFlags((source as IndexType).type) & ObjectFlags.Mapped;
if (result = isRelatedTo(stringNumberSymbolType, target, RecursionFlags.Source, reportErrors && !isDeferredMappedIndex)) {
return result;
}
if (isDeferredMappedIndex) {
const mappedType = (source as IndexType).type as MappedType;
const nameType = getNameTypeFromMappedType(mappedType);
// Unlike on the target side, on the source side we do *not* include the generic part of the `nameType`, since that comes from a
// (potentially anonymous) mapped type local type parameter, so that'd never assign outside the mapped type body, but we still want to
// allow assignments of index types of identical (or similar enough) mapped types.
// eg, `keyof {[X in keyof A]: Obj[X]}` should be assignable to `keyof {[Y in keyof A]: Tup[Y]}` because both map over the same set of keys (`keyof A`).
// Without this source-side breakdown, a `keyof {[X in keyof A]: Obj[X]}` style type won't be assignable to anything except itself, which is much too strict.
const sourceMappedKeys = nameType && isMappedTypeWithKeyofConstraintDeclaration(mappedType) ? getApparentMappedTypeKeys(nameType, mappedType) : (nameType || getConstraintTypeFromMappedType(mappedType));
if (result = isRelatedTo(sourceMappedKeys, target, RecursionFlags.Source, reportErrors)) {
return result;
}
}
}
else if (sourceFlags & TypeFlags.TemplateLiteral && !(targetFlags & TypeFlags.Object)) {
if (!(targetFlags & TypeFlags.TemplateLiteral)) {
const constraint = getBaseConstraintOfType(source);
if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) {
return result;
}
}
}
else if (sourceFlags & TypeFlags.StringMapping) {
if (targetFlags & TypeFlags.StringMapping) {
if ((source as StringMappingType).symbol !== (target as StringMappingType).symbol) {
return Ternary.False;
}
if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) {
return result;
}
}
else {
const constraint = getBaseConstraintOfType(source);
if (constraint && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) {
return result;
}
}
}
else if (sourceFlags & TypeFlags.Conditional) {
// If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive
// conditional type and bail out with a Ternary.Maybe result.
if (isDeeplyNestedType(source, sourceStack, sourceDepth, 10)) {
return Ternary.Maybe;
}
if (targetFlags & TypeFlags.Conditional) {
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.
const sourceParams = (source as ConditionalType).root.inferTypeParameters;
let sourceExtends = (source as ConditionalType).extendsType;
let mapper: TypeMapper | undefined;
if (sourceParams) {
// If the source has infer type parameters, we instantiate them in the context of the target
const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedToWorker);
inferTypes(ctx.inferences, (target as ConditionalType).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
sourceExtends = instantiateType(sourceExtends, ctx.mapper);
mapper = ctx.mapper;
}
if (
isTypeIdenticalTo(sourceExtends, (target as ConditionalType).extendsType) &&
(isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both) || isRelatedTo((target as ConditionalType).checkType, (source as ConditionalType).checkType, RecursionFlags.Both))
) {
if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source as ConditionalType), mapper), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors)) {
result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors);
}
if (result) {
return result;
}
}
}
// conditionals can be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O`
// when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!).
const defaultConstraint = getDefaultConstraintOfConditionalType(source as ConditionalType);
if (defaultConstraint) {
if (result = isRelatedTo(defaultConstraint, target, RecursionFlags.Source, reportErrors)) {
return result;
}
}
// conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way
// more assignments than are desirable (since it maps the source check type to its constraint, it loses information).
const distributiveConstraint = !(targetFlags & TypeFlags.Conditional) && hasNonCircularBaseConstraint(source) ? getConstraintOfDistributiveConditionalType(source as ConditionalType) : undefined;
if (distributiveConstraint) {
resetErrorInfo(saveErrorInfo);
if (result = isRelatedTo(distributiveConstraint, target, RecursionFlags.Source, reportErrors)) {
return result;
}
}
}
else {
// An empty object type is related to any mapped type that includes a '?' modifier.
if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) {
return Ternary.True;
}
if (isGenericMappedType(target)) {
if (isGenericMappedType(source)) {
if (result = mappedTypeRelatedTo(source, target, reportErrors)) {
return result;
}
}
return Ternary.False;
}
const sourceIsPrimitive = !!(sourceFlags & TypeFlags.Primitive);
if (relation !== identityRelation) {
source = getApparentType(source);
sourceFlags = source.flags;
}
else if (isGenericMappedType(source)) {
return Ternary.False;
}
if (
getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target &&
!isTupleType(source) && !(isMarkerType(source) || isMarkerType(target))
) {
// When strictNullChecks is disabled, the element type of the empty array literal is undefinedWideningType,
// and an empty array literal wouldn't be assignable to a `never[]` without this check.
if (isEmptyArrayLiteralType(source)) {
return Ternary.True;
}
// We have type references to the same generic type, and the type references are not marker
// type references (which are intended by be compared structurally). Obtain the variance
// information for the type parameters and relate the type arguments accordingly.
const variances = getVariances((source as TypeReference).target);
// We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This
// effectively means we measure variance only from type parameter occurrences that aren't nested in
// recursive instantiations of the generic type.
if (variances === emptyArray) {
return Ternary.Unknown;
}
const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState);
if (varianceResult !== undefined) {
return varianceResult;
}
}
else if (isReadonlyArrayType(target) ? everyType(source, isArrayOrTupleType) : isArrayType(target) && everyType(source, t => isTupleType(t) && !t.target.readonly)) {
if (relation !== identityRelation) {
return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, reportErrors);
}
else {
// By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple
// or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction
return Ternary.False;
}
}
else if (isGenericTupleType(source) && isTupleType(target) && !isGenericTupleType(target)) {
const constraint = getBaseConstraintOrType(source);
if (constraint !== source) {
return isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors);
}
}
// A fresh empty object type is never a subtype of a non-empty object type. This ensures fresh({}) <: { [x: string]: xxx }
// but not vice-versa. Without this rule, those types would be mutual subtypes.
else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) {
return Ternary.False;
}
// Even if relationship doesn't hold for unions, intersections, or generic type references,
// it may hold in a structural comparison.
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
// relates to X. Thus, we include intersection types on the source side here.
if (sourceFlags & (TypeFlags.Object | TypeFlags.Intersection) && targetFlags & TypeFlags.Object) {
// Report structural errors only if we haven't reported any errors yet
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive;
result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, intersectionState);
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors, intersectionState);
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors, intersectionState);
if (result) {
result &= indexSignaturesRelatedTo(source, target, sourceIsPrimitive, reportStructuralErrors, intersectionState);
}
}
}
if (varianceCheckFailed && result) {
errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false
}
else if (result) {
return result;
}
}
// If S is an object type and T is a discriminated union, S may be related to T if
// there exists a constituent of T for every combination of the discriminants of S
// with respect to T. We do not report errors here, as we will use the existing
// error result from checking each constituent of the union.
if (sourceFlags & (TypeFlags.Object | TypeFlags.Intersection) && targetFlags & TypeFlags.Union) {
const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Substitution);
if (objectOnlyTarget.flags & TypeFlags.Union) {
const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType);
if (result) {
return result;
}
}
}
}
return Ternary.False;
function countMessageChainBreadth(info: DiagnosticMessageChain[] | undefined): number {
if (!info) return 0;
return reduceLeft(info, (value, chain) => value + 1 + countMessageChainBreadth(chain.next), 0);
}
function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], intersectionState: IntersectionState) {
if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState)) {
return result;
}
if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) {
// If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we
// have to allow a structural fallback check
// We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially
// be assuming identity of the type parameter.
originalErrorInfo = undefined;
resetErrorInfo(saveErrorInfo);
return undefined;
}
const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances);
varianceCheckFailed = !allowStructuralFallback;
// The type arguments did not relate appropriately, but it may be because we have no variance
// information (in which case typeArgumentsRelatedTo defaulted to covariance for all type
// arguments). It might also be the case that the target type has a 'void' type argument for
// a covariant type parameter that is only used in return positions within the generic type
// (in which case any type argument is permitted on the source side). In those cases we proceed
// with a structural comparison. Otherwise, we know for certain the instantiations aren't
// related and we can return here.
if (variances !== emptyArray && !allowStructuralFallback) {
// In some cases generic types that are covariant in regular type checking mode become
// invariant in --strictFunctionTypes mode because one or more type parameters are used in
// both co- and contravariant positions. In order to make it easier to diagnose *why* such
// types are invariant, if any of the type parameters are invariant we reset the reported
// errors and instead force a structural comparison (which will include elaborations that
// reveal the reason).
// We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`,
// we can return `False` early here to skip calculating the structural error message we don't need.
if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) {
return Ternary.False;
}
// We remember the original error information so we can restore it in case the structural
// comparison unexpectedly succeeds. This can happen when the structural comparison result
// is a Ternary.Maybe for example caused by the recursion depth limiter.
originalErrorInfo = errorInfo;
resetErrorInfo(saveErrorInfo);
}
}
}
// A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is
// related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice
// that S and T are contra-variant whereas X and Y are co-variant.
function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary {
const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) :
getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target));
if (modifiersRelated) {
let result: Ternary;
const targetConstraint = getConstraintTypeFromMappedType(target);
const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMapper : reportUnreliableMapper);
if (result = isRelatedTo(targetConstraint, sourceConstraint, RecursionFlags.Both, reportErrors)) {
const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]);
if (instantiateType(getNameTypeFromMappedType(source), mapper) === instantiateType(getNameTypeFromMappedType(target), mapper)) {
return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), RecursionFlags.Both, reportErrors);
}
}
}
return Ternary.False;
}
function typeRelatedToDiscriminatedType(source: Type, target: UnionType) {
// 1. Generate the combinations of discriminant properties & types 'source' can satisfy.
// a. If the number of combinations is above a set limit, the comparison is too complex.
// 2. Filter 'target' to the subset of types whose discriminants exist in the matrix.
// a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related.
// 3. For each type in the filtered 'target', determine if all non-discriminant properties of
// 'target' are related to a property in 'source'.
//
// NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts
// for examples.
const sourceProperties = getPropertiesOfType(source);
const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target);
if (!sourcePropertiesFiltered) return Ternary.False;
// Though we could compute the number of combinations as we generate
// the matrix, this would incur additional memory overhead due to
// array allocations. To reduce this overhead, we first compute
// the number of combinations to ensure we will not surpass our
// fixed limit before incurring the cost of any allocations:
let numCombinations = 1;
for (const sourceProperty of sourcePropertiesFiltered) {
numCombinations *= countTypes(getNonMissingTypeOfSymbol(sourceProperty));
if (numCombinations > 25) {
// We've reached the complexity limit.
tracing?.instant(tracing.Phase.CheckTypes, "typeRelatedToDiscriminatedType_DepthLimit", { sourceId: source.id, targetId: target.id, numCombinations });
return Ternary.False;
}
}
// Compute the set of types for each discriminant property.
const sourceDiscriminantTypes: Type[][] = new Array(sourcePropertiesFiltered.length);
const excludedProperties = new Set<__String>();
for (let i = 0; i < sourcePropertiesFiltered.length; i++) {
const sourceProperty = sourcePropertiesFiltered[i];
const sourcePropertyType = getNonMissingTypeOfSymbol(sourceProperty);
sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union
? (sourcePropertyType as UnionType).types
: [sourcePropertyType];
excludedProperties.add(sourceProperty.escapedName);
}
// Match each combination of the cartesian product of discriminant properties to one or more
// constituents of 'target'. If any combination does not have a match then 'source' is not relatable.
const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes);
const matchingTypes: Type[] = [];
for (const combination of discriminantCombinations) {
let hasMatch = false;
outer:
for (const type of target.types) {
for (let i = 0; i < sourcePropertiesFiltered.length; i++) {
const sourceProperty = sourcePropertiesFiltered[i];
const targetProperty = getPropertyOfType(type, sourceProperty.escapedName);
if (!targetProperty) continue outer;
if (sourceProperty === targetProperty) continue;
// We compare the source property to the target in the context of a single discriminant type.
const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, IntersectionState.None, /*skipOptional*/ strictNullChecks || relation === comparableRelation);
// If the target property could not be found, or if the properties were not related,
// then this constituent is not a match.
if (!related) {
continue outer;
}
}
pushIfUnique(matchingTypes, type, equateValues);
hasMatch = true;
}
if (!hasMatch) {
// We failed to match any type for this combination.
return Ternary.False;
}
}
// Compare the remaining non-discriminant properties of each match.
let result = Ternary.True;
for (const type of matchingTypes) {
result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, /*optionalsOnly*/ false, IntersectionState.None);
if (result) {
result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportErrors*/ false, IntersectionState.None);
if (result) {
result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportErrors*/ false, IntersectionState.None);
if (result && !(isTupleType(source) && isTupleType(type))) {
// Comparing numeric index types when both `source` and `type` are tuples is unnecessary as the
// element types should be sufficiently covered by `propertiesRelatedTo`. It also causes problems
// with index type assignability as the types for the excluded discriminants are still included
// in the index type.
result &= indexSignaturesRelatedTo(source, type, /*sourceIsPrimitive*/ false, /*reportErrors*/ false, IntersectionState.None);
}
}
}
if (!result) {
return result;
}
}
return result;
}
function excludeProperties(properties: Symbol[], excludedProperties: Set<__String> | undefined) {
if (!excludedProperties || properties.length === 0) return properties;
let result: Symbol[] | undefined;
for (let i = 0; i < properties.length; i++) {
if (!excludedProperties.has(properties[i].escapedName)) {
if (result) {
result.push(properties[i]);
}
}
else if (!result) {
result = properties.slice(0, i);
}
}
return result || properties;
}
function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial);
const effectiveTarget = addOptionality(getNonMissingTypeOfSymbol(targetProp), /*isProperty*/ false, targetIsOptional);
// source could resolve to `any` and that's not related to `unknown` target under strict subtype relation
if (effectiveTarget.flags & (relation === strictSubtypeRelation ? TypeFlags.Any : TypeFlags.AnyOrUnknown)) {
return Ternary.True;
}
const effectiveSource = getTypeOfSourceProperty(sourceProp);
return isRelatedTo(effectiveSource, effectiveTarget, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState, skipOptional: boolean): Ternary {
const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp);
const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp);
if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) {
if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) {
if (reportErrors) {
if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) {
reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp));
}
else {
reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), typeToString(sourcePropFlags & ModifierFlags.Private ? target : source));
}
}
return Ternary.False;
}
}
else if (targetPropFlags & ModifierFlags.Protected) {
if (!isValidOverrideOf(sourceProp, targetProp)) {
if (reportErrors) {
reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target));
}
return Ternary.False;
}
}
else if (sourcePropFlags & ModifierFlags.Protected) {
if (reportErrors) {
reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, symbolToString(targetProp), typeToString(source), typeToString(target));
}
return Ternary.False;
}
// Ensure {readonly a: whatever} is not a subtype of {a: whatever},
// while {a: whatever} is a subtype of {readonly a: whatever}.
// This ensures the subtype relationship is ordered, and preventing declaration order
// from deciding which type "wins" in union subtype reduction.
// They're still assignable to one another, since `readonly` doesn't affect assignability.
// This is only applied during the strictSubtypeRelation -- currently used in subtype reduction
if (
relation === strictSubtypeRelation &&
isReadonlySymbol(sourceProp) && !isReadonlySymbol(targetProp)
) {
return Ternary.False;
}
// If the target comes from a partial union prop, allow `undefined` in the target type
const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState);
if (!related) {
if (reportErrors) {
reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
}
return Ternary.False;
}
// When checking for comparability, be more lenient with optional properties.
if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && targetProp.flags & SymbolFlags.ClassMember && !(targetProp.flags & SymbolFlags.Optional)) {
// TypeScript 1.0 spec (April 2014): 3.8.3
// S is a subtype of a type T, and T is a supertype of S if ...
// S' and T are object types and, for each member M in T..
// M is a property and S' contains a property N where
// if M is a required property, N is also a required property
// (M - property in T)
// (N - property in S)
if (reportErrors) {
reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, symbolToString(targetProp), typeToString(source), typeToString(target));
}
return Ternary.False;
}
return related;
}
function reportUnmatchedProperty(source: Type, target: Type, unmatchedProperty: Symbol, requireOptionalProperties: boolean) {
let shouldSkipElaboration = false;
// give specific error in case where private names have the same description
if (
unmatchedProperty.valueDeclaration
&& isNamedDeclaration(unmatchedProperty.valueDeclaration)
&& isPrivateIdentifier(unmatchedProperty.valueDeclaration.name)
&& source.symbol
&& source.symbol.flags & SymbolFlags.Class
) {
const privateIdentifierDescription = unmatchedProperty.valueDeclaration.name.escapedText;
const symbolTableKey = getSymbolNameForPrivateIdentifier(source.symbol, privateIdentifierDescription);
if (symbolTableKey && getPropertyOfType(source, symbolTableKey)) {
const sourceName = factory.getDeclarationName(source.symbol.valueDeclaration);
const targetName = factory.getDeclarationName(target.symbol.valueDeclaration);
reportError(
Diagnostics.Property_0_in_type_1_refers_to_a_different_member_that_cannot_be_accessed_from_within_type_2,
diagnosticName(privateIdentifierDescription),
diagnosticName(sourceName.escapedText === "" ? anon : sourceName),
diagnosticName(targetName.escapedText === "" ? anon : targetName),
);
return;
}
}
const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false));
if (
!headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code &&
headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code)
) {
shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it
}
if (props.length === 1) {
const propName = symbolToString(unmatchedProperty, /*enclosingDeclaration*/ undefined, SymbolFlags.None, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteComputedProps);
reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target));
if (length(unmatchedProperty.declarations)) {
associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName));
}
if (shouldSkipElaboration && errorInfo) {
overrideNextErrorInfo++;
}
}
else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) {
if (props.length > 5) { // arbitrary cutoff for too-long list form
reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4);
}
else {
reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, typeToString(source), typeToString(target), map(props, p => symbolToString(p)).join(", "));
}
if (shouldSkipElaboration && errorInfo) {
overrideNextErrorInfo++;
}
}
// No array like or unmatched property error - just issue top level error (errorInfo = undefined)
}
function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, optionalsOnly: boolean, intersectionState: IntersectionState): Ternary {
if (relation === identityRelation) {
return propertiesIdenticalTo(source, target, excludedProperties);
}
let result = Ternary.True;
if (isTupleType(target)) {
if (isArrayOrTupleType(source)) {
if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) {
return Ternary.False;
}
const sourceArity = getTypeReferenceArity(source);
const targetArity = getTypeReferenceArity(target);
const sourceRestFlag = isTupleType(source) ? source.target.combinedFlags & ElementFlags.Rest : ElementFlags.Rest;
const targetHasRestElement = !!(target.target.combinedFlags & ElementFlags.Variable);
const sourceMinLength = isTupleType(source) ? source.target.minLength : 0;
const targetMinLength = target.target.minLength;
if (!sourceRestFlag && sourceArity < targetMinLength) {
if (reportErrors) {
reportError(Diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength);
}
return Ternary.False;
}
if (!targetHasRestElement && targetArity < sourceMinLength) {
if (reportErrors) {
reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity);
}
return Ternary.False;
}
if (!targetHasRestElement && (sourceRestFlag || targetArity < sourceArity)) {
if (reportErrors) {
if (sourceMinLength < targetMinLength) {
reportError(Diagnostics.Target_requires_0_element_s_but_source_may_have_fewer, targetMinLength);
}
else {
reportError(Diagnostics.Target_allows_only_0_element_s_but_source_may_have_more, targetArity);
}
}
return Ternary.False;
}
const sourceTypeArguments = getTypeArguments(source);
const targetTypeArguments = getTypeArguments(target);
const targetStartCount = getStartElementCount(target.target, ElementFlags.NonRest);
const targetEndCount = getEndElementCount(target.target, ElementFlags.NonRest);
let canExcludeDiscriminants = !!excludedProperties;
for (let sourcePosition = 0; sourcePosition < sourceArity; sourcePosition++) {
const sourceFlags = isTupleType(source) ? source.target.elementFlags[sourcePosition] : ElementFlags.Rest;
const sourcePositionFromEnd = sourceArity - 1 - sourcePosition;
const targetPosition = targetHasRestElement && sourcePosition >= targetStartCount
? targetArity - 1 - Math.min(sourcePositionFromEnd, targetEndCount)
: sourcePosition;
const targetFlags = target.target.elementFlags[targetPosition];
if (targetFlags & ElementFlags.Variadic && !(sourceFlags & ElementFlags.Variadic)) {
if (reportErrors) {
reportError(Diagnostics.Source_provides_no_match_for_variadic_element_at_position_0_in_target, targetPosition);
}
return Ternary.False;
}
if (sourceFlags & ElementFlags.Variadic && !(targetFlags & ElementFlags.Variable)) {
if (reportErrors) {
reportError(Diagnostics.Variadic_element_at_position_0_in_source_does_not_match_element_at_position_1_in_target, sourcePosition, targetPosition);
}
return Ternary.False;
}
if (targetFlags & ElementFlags.Required && !(sourceFlags & ElementFlags.Required)) {
if (reportErrors) {
reportError(Diagnostics.Source_provides_no_match_for_required_element_at_position_0_in_target, targetPosition);
}
return Ternary.False;
}
// We can only exclude discriminant properties if we have not yet encountered a variable-length element.
if (canExcludeDiscriminants) {
if (sourceFlags & ElementFlags.Variable || targetFlags & ElementFlags.Variable) {
canExcludeDiscriminants = false;
}
if (canExcludeDiscriminants && excludedProperties?.has(("" + sourcePosition) as __String)) {
continue;
}
}
const sourceType = removeMissingType(sourceTypeArguments[sourcePosition], !!(sourceFlags & targetFlags & ElementFlags.Optional));
const targetType = targetTypeArguments[targetPosition];
const targetCheckType = sourceFlags & ElementFlags.Variadic && targetFlags & ElementFlags.Rest ? createArrayType(targetType) :
removeMissingType(targetType, !!(targetFlags & ElementFlags.Optional));
const related = isRelatedTo(sourceType, targetCheckType, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
if (!related) {
if (reportErrors && (targetArity > 1 || sourceArity > 1)) {
if (targetHasRestElement && sourcePosition >= targetStartCount && sourcePositionFromEnd >= targetEndCount && targetStartCount !== sourceArity - targetEndCount - 1) {
reportIncompatibleError(Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, targetStartCount, sourceArity - targetEndCount - 1, targetPosition);
}
else {
reportIncompatibleError(Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, sourcePosition, targetPosition);
}
}
return Ternary.False;
}
result &= related;
}
return result;
}
if (target.target.combinedFlags & ElementFlags.Variable) {
return Ternary.False;
}
}
const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source);
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false);
if (unmatchedProperty) {
if (reportErrors && shouldReportUnmatchedPropertyError(source, target)) {
reportUnmatchedProperty(source, target, unmatchedProperty, requireOptionalProperties);
}
return Ternary.False;
}
if (isObjectLiteralType(target)) {
for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) {
if (!getPropertyOfObjectType(target, sourceProp.escapedName)) {
const sourceType = getTypeOfSymbol(sourceProp);
if (!(sourceType.flags & TypeFlags.Undefined)) {
if (reportErrors) {
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target));
}
return Ternary.False;
}
}
}
}
// We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_
// from the target union, across all members
const properties = getPropertiesOfType(target);
const numericNamesOnly = isTupleType(source) && isTupleType(target);
for (const targetProp of excludeProperties(properties, excludedProperties)) {
const name = targetProp.escapedName;
if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length") && (!optionalsOnly || targetProp.flags & SymbolFlags.Optional)) {
const sourceProp = getPropertyOfType(source, name);
if (sourceProp && sourceProp !== targetProp) {
const related = propertyRelatedTo(source, target, sourceProp, targetProp, getNonMissingTypeOfSymbol, reportErrors, intersectionState, relation === comparableRelation);
if (!related) {
return Ternary.False;
}
result &= related;
}
}
}
return result;
}
function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: Set<__String> | undefined): Ternary {
if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) {
return Ternary.False;
}
const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties);
const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties);
if (sourceProperties.length !== targetProperties.length) {
return Ternary.False;
}
let result = Ternary.True;
for (const sourceProp of sourceProperties) {
const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName);
if (!targetProp) {
return Ternary.False;
}
const related = compareProperties(sourceProp, targetProp, isRelatedTo);
if (!related) {
return Ternary.False;
}
result &= related;
}
return result;
}
function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
if (relation === identityRelation) {
return signaturesIdenticalTo(source, target, kind);
}
if (target === anyFunctionType || source === anyFunctionType) {
return Ternary.True;
}
const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration);
const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration);
const sourceSignatures = getSignaturesOfType(
source,
(sourceIsJSConstructor && kind === SignatureKind.Construct) ?
SignatureKind.Call : kind,
);
const targetSignatures = getSignaturesOfType(
target,
(targetIsJSConstructor && kind === SignatureKind.Construct) ?
SignatureKind.Call : kind,
);
if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) {
const sourceIsAbstract = !!(sourceSignatures[0].flags & SignatureFlags.Abstract);
const targetIsAbstract = !!(targetSignatures[0].flags & SignatureFlags.Abstract);
if (sourceIsAbstract && !targetIsAbstract) {
// An abstract constructor type is not assignable to a non-abstract constructor type
// as it would otherwise be possible to new an abstract class. Note that the assignability
// check we perform for an extends clause excludes construct signatures from the target,
// so this check never proceeds.
if (reportErrors) {
reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type);
}
return Ternary.False;
}
if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) {
return Ternary.False;
}
}
let result = Ternary.True;
const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn;
const sourceObjectFlags = getObjectFlags(source);
const targetObjectFlags = getObjectFlags(target);
if (
sourceObjectFlags & ObjectFlags.Instantiated && targetObjectFlags & ObjectFlags.Instantiated && source.symbol === target.symbol ||
sourceObjectFlags & ObjectFlags.Reference && targetObjectFlags & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target
) {
// We have instantiations of the same anonymous type (which typically will be the type of a
// method). Simply do a pairwise comparison of the signatures in the two signature lists instead
// of the much more expensive N * M comparison matrix we explore below. We erase type parameters
// as they are known to always be the same.
Debug.assertEqual(sourceSignatures.length, targetSignatures.length);
for (let i = 0; i < targetSignatures.length; i++) {
const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, intersectionState, incompatibleReporter(sourceSignatures[i], targetSignatures[i]));
if (!related) {
return Ternary.False;
}
result &= related;
}
}
else if (sourceSignatures.length === 1 && targetSignatures.length === 1) {
// For simple functions (functions with a single signature) we only erase type parameters for
// the comparable relation. Otherwise, if the source signature is generic, we instantiate it
// in the context of the target signature before checking the relationship. Ideally we'd do
// this regardless of the number of signatures, but the potential costs are prohibitive due
// to the quadratic nature of the logic below.
const eraseGenerics = relation === comparableRelation;
const sourceSignature = first(sourceSignatures);
const targetSignature = first(targetSignatures);
result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, intersectionState, incompatibleReporter(sourceSignature, targetSignature));
if (
!result && reportErrors && kind === SignatureKind.Construct && (sourceObjectFlags & targetObjectFlags) &&
(targetSignature.declaration?.kind === SyntaxKind.Constructor || sourceSignature.declaration?.kind === SyntaxKind.Constructor)
) {
const constructSignatureToString = (signature: Signature) => signatureToString(signature, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrowStyleSignature, kind);
reportError(Diagnostics.Type_0_is_not_assignable_to_type_1, constructSignatureToString(sourceSignature), constructSignatureToString(targetSignature));
reportError(Diagnostics.Types_of_construct_signatures_are_incompatible);
return result;
}
}
else {
outer:
for (const t of targetSignatures) {
const saveErrorInfo = captureErrorCalculationState();
// Only elaborate errors from the first failure
let shouldElaborateErrors = reportErrors;
for (const s of sourceSignatures) {
const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, intersectionState, incompatibleReporter(s, t));
if (related) {
result &= related;
resetErrorInfo(saveErrorInfo);
continue outer;
}
shouldElaborateErrors = false;
}
if (shouldElaborateErrors) {
reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, typeToString(source), signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind));
}
return Ternary.False;
}
}
return result;
}
function shouldReportUnmatchedPropertyError(source: Type, target: Type): boolean {
const typeCallSignatures = getSignaturesOfStructuredType(source, SignatureKind.Call);
const typeConstructSignatures = getSignaturesOfStructuredType(source, SignatureKind.Construct);
const typeProperties = getPropertiesOfObjectType(source);
if ((typeCallSignatures.length || typeConstructSignatures.length) && !typeProperties.length) {
if (
(getSignaturesOfType(target, SignatureKind.Call).length && typeCallSignatures.length) ||
(getSignaturesOfType(target, SignatureKind.Construct).length && typeConstructSignatures.length)
) {
return true; // target has similar signature kinds to source, still focus on the unmatched property
}
return false;
}
return true;
}
function reportIncompatibleCallSignatureReturn(siga: Signature, sigb: Signature) {
if (siga.parameters.length === 0 && sigb.parameters.length === 0) {
return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target));
}
return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target));
}
function reportIncompatibleConstructSignatureReturn(siga: Signature, sigb: Signature) {
if (siga.parameters.length === 0 && sigb.parameters.length === 0) {
return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target));
}
return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target));
}
/**
* See signatureAssignableTo, compareSignaturesIdentical
*/
function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, intersectionState: IntersectionState, incompatibleReporter: (source: Type, target: Type) => void): Ternary {
const checkMode = relation === subtypeRelation ? SignatureCheckMode.StrictTopSignature :
relation === strictSubtypeRelation ? SignatureCheckMode.StrictTopSignature | SignatureCheckMode.StrictArity :
SignatureCheckMode.None;
return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, checkMode, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper);
function isRelatedToWorker(source: Type, target: Type, reportErrors?: boolean) {
return isRelatedTo(source, target, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
}
}
function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary {
const sourceSignatures = getSignaturesOfType(source, kind);
const targetSignatures = getSignaturesOfType(target, kind);
if (sourceSignatures.length !== targetSignatures.length) {
return Ternary.False;
}
let result = Ternary.True;
for (let i = 0; i < sourceSignatures.length; i++) {
const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo);
if (!related) {
return Ternary.False;
}
result &= related;
}
return result;
}
function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
let result = Ternary.True;
const keyType = targetInfo.keyType;
const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source as IntersectionType) : getPropertiesOfObjectType(source);
for (const prop of props) {
// Skip over ignored JSX and symbol-named members
if (isIgnoredJsxProperty(source, prop)) {
continue;
}
if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), keyType)) {
const propType = getNonMissingTypeOfSymbol(prop);
const type = exactOptionalPropertyTypes || propType.flags & TypeFlags.Undefined || keyType === numberType || !(prop.flags & SymbolFlags.Optional)
? propType
: getTypeWithFacts(propType, TypeFacts.NEUndefined);
const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop));
}
return Ternary.False;
}
result &= related;
}
}
for (const info of getIndexInfosOfType(source)) {
if (isApplicableIndexType(info.keyType, keyType)) {
const related = indexInfoRelatedTo(info, targetInfo, reportErrors, intersectionState);
if (!related) {
return Ternary.False;
}
result &= related;
}
}
return result;
}
function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState) {
const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState);
if (!related && reportErrors) {
if (sourceInfo.keyType === targetInfo.keyType) {
reportError(Diagnostics._0_index_signatures_are_incompatible, typeToString(sourceInfo.keyType));
}
else {
reportError(Diagnostics._0_and_1_index_signatures_are_incompatible, typeToString(sourceInfo.keyType), typeToString(targetInfo.keyType));
}
}
return related;
}
function indexSignaturesRelatedTo(source: Type, target: Type, sourceIsPrimitive: boolean, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
if (relation === identityRelation) {
return indexSignaturesIdenticalTo(source, target);
}
const indexInfos = getIndexInfosOfType(target);
const targetHasStringIndex = some(indexInfos, info => info.keyType === stringType);
let result = Ternary.True;
for (const targetInfo of indexInfos) {
const related = relation !== strictSubtypeRelation && !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True :
isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, RecursionFlags.Both, reportErrors) :
typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState);
if (!related) {
return Ternary.False;
}
result &= related;
}
return result;
}
function typeRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType);
if (sourceInfo) {
return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors, intersectionState);
}
// Intersection constituents are never considered to have an inferred index signature. Also, in the strict subtype relation,
// only fresh object literals are considered to have inferred index signatures. This ensures { [x: string]: xxx } <: {} but
// not vice-versa. Without this rule, those types would be mutual strict subtypes.
if (!(intersectionState & IntersectionState.Source) && (relation !== strictSubtypeRelation || getObjectFlags(source) & ObjectFlags.FreshLiteral) && isObjectTypeWithInferableIndex(source)) {
return membersRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState);
}
if (reportErrors) {
reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source));
}
return Ternary.False;
}
function indexSignaturesIdenticalTo(source: Type, target: Type): Ternary {
const sourceInfos = getIndexInfosOfType(source);
const targetInfos = getIndexInfosOfType(target);
if (sourceInfos.length !== targetInfos.length) {
return Ternary.False;
}
for (const targetInfo of targetInfos) {
const sourceInfo = getIndexInfoOfType(source, targetInfo.keyType);
if (!(sourceInfo && isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both) && sourceInfo.isReadonly === targetInfo.isReadonly)) {
return Ternary.False;
}
}
return Ternary.True;
}
function constructorVisibilitiesAreCompatible(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) {
if (!sourceSignature.declaration || !targetSignature.declaration) {
return true;
}
const sourceAccessibility = getSelectedEffectiveModifierFlags(sourceSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier);
const targetAccessibility = getSelectedEffectiveModifierFlags(targetSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier);
// A public, protected and private signature is assignable to a private signature.
if (targetAccessibility === ModifierFlags.Private) {
return true;
}
// A public and protected signature is assignable to a protected signature.
if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) {
return true;
}
// Only a public signature is assignable to public signature.
if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) {
return true;
}
if (reportErrors) {
reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility));
}
return false;
}
}
function typeCouldHaveTopLevelSingletonTypes(type: Type): boolean {
// Okay, yes, 'boolean' is a union of 'true | false', but that's not useful
// in error reporting scenarios. If you need to use this function but that detail matters,
// feel free to add a flag.
if (type.flags & TypeFlags.Boolean) {
return false;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
return !!forEach((type as IntersectionType).types, typeCouldHaveTopLevelSingletonTypes);
}
if (type.flags & TypeFlags.Instantiable) {
const constraint = getConstraintOfType(type);
if (constraint && constraint !== type) {
return typeCouldHaveTopLevelSingletonTypes(constraint);
}
}
return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) || !!(type.flags & TypeFlags.StringMapping);
}
function getExactOptionalUnassignableProperties(source: Type, target: Type) {
if (isTupleType(source) && isTupleType(target)) return emptyArray;
return getPropertiesOfType(target)
.filter(targetProp => isExactOptionalPropertyMismatch(getTypeOfPropertyOfType(source, targetProp.escapedName), getTypeOfSymbol(targetProp)));
}
function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) {
return !!source && !!target && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target);
}
function getExactOptionalProperties(type: Type) {
return getPropertiesOfType(type).filter(targetProp => containsMissingType(getTypeOfSymbol(targetProp)));
}
function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) {
return findMatchingDiscriminantType(source, target, isRelatedTo) ||
findMatchingTypeReferenceOrTypeAliasReference(source, target) ||
findBestTypeForObjectLiteral(source, target) ||
findBestTypeForInvokable(source, target) ||
findMostOverlappyType(source, target);
}
function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: (readonly [() => Type, __String])[], related: (source: Type, target: Type) => boolean | Ternary) {
const types = target.types;
const include: Ternary[] = types.map(t => t.flags & TypeFlags.Primitive ? Ternary.False : Ternary.True);
for (const [getDiscriminatingType, propertyName] of discriminators) {
// If the remaining target types include at least one with a matching discriminant, eliminate those that
// have non-matching discriminants. This ensures that we ignore erroneous discriminators and gradually
// refine the target set without eliminating every constituent (which would lead to `never`).
let matched = false;
for (let i = 0; i < types.length; i++) {
if (include[i]) {
const targetType = getTypeOfPropertyOrIndexSignatureOfType(types[i], propertyName);
if (targetType) {
if (someType(getDiscriminatingType(), t => !!related(t, targetType))) {
matched = true;
}
else {
include[i] = Ternary.Maybe;
}
}
}
}
// Turn each Ternary.Maybe into Ternary.False if there was a match. Otherwise, revert to Ternary.True.
for (let i = 0; i < types.length; i++) {
if (include[i] === Ternary.Maybe) {
include[i] = matched ? Ternary.False : Ternary.True;
}
}
}
const filtered = contains(include, Ternary.False) ? getUnionType(types.filter((_, i) => include[i]), UnionReduction.None) : target;
return filtered.flags & TypeFlags.Never ? target : filtered;
}
/**
* A type is 'weak' if it is an object type with at least one optional property
* and no required properties, call/construct signatures or index signatures
*/
function isWeakType(type: Type): boolean {
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(type as ObjectType);
return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && resolved.indexInfos.length === 0 &&
resolved.properties.length > 0 && every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional));
}
if (type.flags & TypeFlags.Substitution) {
return isWeakType((type as SubstitutionType).baseType);
}
if (type.flags & TypeFlags.Intersection) {
return every((type as IntersectionType).types, isWeakType);
}
return false;
}
function hasCommonProperties(source: Type, target: Type, isComparingJsxAttributes: boolean) {
for (const prop of getPropertiesOfType(source)) {
if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) {
return true;
}
}
return false;
}
function getVariances(type: GenericType): VarianceFlags[] {
// Arrays and tuples are known to be covariant, no need to spend time computing this.
return type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple ?
arrayVariances :
getVariancesWorker(type.symbol, type.typeParameters);
}
function getAliasVariances(symbol: Symbol) {
return getVariancesWorker(symbol, getSymbolLinks(symbol).typeParameters);
}
// Return an array containing the variance of each type parameter. The variance is effectively
// a digest of the type comparisons that occur for each type argument when instantiations of the
// generic type are structurally compared. We infer the variance information by comparing
// instantiations of the generic type for type arguments with known relations. The function
// returns the emptyArray singleton when invoked recursively for the given generic type.
function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray): VarianceFlags[] {
const links = getSymbolLinks(symbol);
if (!links.variances) {
tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getTypeId(getDeclaredTypeOfSymbol(symbol)) });
const oldVarianceComputation = inVarianceComputation;
const saveResolutionStart = resolutionStart;
if (!inVarianceComputation) {
inVarianceComputation = true;
resolutionStart = resolutionTargets.length;
}
links.variances = emptyArray;
const variances = [];
for (const tp of typeParameters) {
const modifiers = getTypeParameterModifiers(tp);
let variance = modifiers & ModifierFlags.Out ?
modifiers & ModifierFlags.In ? VarianceFlags.Invariant : VarianceFlags.Covariant :
modifiers & ModifierFlags.In ? VarianceFlags.Contravariant : undefined;
if (variance === undefined) {
let unmeasurable = false;
let unreliable = false;
const oldHandler = outofbandVarianceMarkerHandler;
outofbandVarianceMarkerHandler = onlyUnreliable => onlyUnreliable ? unreliable = true : unmeasurable = true;
// We first compare instantiations where the type parameter is replaced with
// marker types that have a known subtype relationship. From this we can infer
// invariance, covariance, contravariance or bivariance.
const typeWithSuper = createMarkerType(symbol, tp, markerSuperType);
const typeWithSub = createMarkerType(symbol, tp, markerSubType);
variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) |
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0);
// If the instantiations appear to be related bivariantly it may be because the
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
// type). To determine this we compare instantiations where the type parameter is
// replaced with marker types that are known to be unrelated.
if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(symbol, tp, markerOtherType), typeWithSuper)) {
variance = VarianceFlags.Independent;
}
outofbandVarianceMarkerHandler = oldHandler;
if (unmeasurable || unreliable) {
if (unmeasurable) {
variance |= VarianceFlags.Unmeasurable;
}
if (unreliable) {
variance |= VarianceFlags.Unreliable;
}
}
}
variances.push(variance);
}
if (!oldVarianceComputation) {
inVarianceComputation = false;
resolutionStart = saveResolutionStart;
}
links.variances = variances;
tracing?.pop({ variances: variances.map(Debug.formatVariance) });
}
return links.variances;
}
function createMarkerType(symbol: Symbol, source: TypeParameter, target: Type) {
const mapper = makeUnaryTypeMapper(source, target);
const type = getDeclaredTypeOfSymbol(symbol);
if (isErrorType(type)) {
return type;
}
const result = symbol.flags & SymbolFlags.TypeAlias ?
getTypeAliasInstantiation(symbol, instantiateTypes(getSymbolLinks(symbol).typeParameters!, mapper)) :
createTypeReference(type as GenericType, instantiateTypes((type as GenericType).typeParameters, mapper));
markerTypes.add(getTypeId(result));
return result;
}
function isMarkerType(type: Type) {
return markerTypes.has(getTypeId(type));
}
function getTypeParameterModifiers(tp: TypeParameter): ModifierFlags {
return reduceLeft(tp.symbol?.declarations, (modifiers, d) => modifiers | getEffectiveModifierFlags(d), ModifierFlags.None) & (ModifierFlags.In | ModifierFlags.Out | ModifierFlags.Const);
}
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
// See comment at call in recursiveTypeRelatedTo for when this case matters.
function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean {
for (let i = 0; i < variances.length; i++) {
if ((variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Covariant && typeArguments[i].flags & TypeFlags.Void) {
return true;
}
}
return false;
}
function isUnconstrainedTypeParameter(type: Type) {
return type.flags & TypeFlags.TypeParameter && !getConstraintOfTypeParameter(type as TypeParameter);
}
function isNonDeferredTypeReference(type: Type): type is TypeReference {
return !!(getObjectFlags(type) & ObjectFlags.Reference) && !(type as TypeReference).node;
}
function isTypeReferenceWithGenericArguments(type: Type): boolean {
return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t));
}
function getGenericTypeReferenceRelationKey(source: TypeReference, target: TypeReference, postFix: string, ignoreConstraints: boolean) {
const typeParameters: Type[] = [];
let constraintMarker = "";
const sourceId = getTypeReferenceId(source, 0);
const targetId = getTypeReferenceId(target, 0);
return `${constraintMarker}${sourceId},${targetId}${postFix}`;
// getTypeReferenceId(A) returns "111=0-12=1"
// where A.id=111 and number.id=12
function getTypeReferenceId(type: TypeReference, depth = 0) {
let result = "" + type.target.id;
for (const t of getTypeArguments(type)) {
if (t.flags & TypeFlags.TypeParameter) {
if (ignoreConstraints || isUnconstrainedTypeParameter(t)) {
let index = typeParameters.indexOf(t);
if (index < 0) {
index = typeParameters.length;
typeParameters.push(t);
}
result += "=" + index;
continue;
}
// We mark type references that reference constrained type parameters such that we know to obtain
// and look for a "broadest equivalent key" in the cache.
constraintMarker = "*";
}
else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) {
result += "<" + getTypeReferenceId(t as TypeReference, depth + 1) + ">";
continue;
}
result += "-" + t.id;
}
return result;
}
}
/**
* To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters.
* For other cases, the types ids are used.
*/
function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: Map, ignoreConstraints: boolean) {
if (relation === identityRelation && source.id > target.id) {
const temp = source;
source = target;
target = temp;
}
const postFix = intersectionState ? ":" + intersectionState : "";
return isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) ?
getGenericTypeReferenceRelationKey(source as TypeReference, target as TypeReference, postFix, ignoreConstraints) :
`${source.id},${target.id}${postFix}`;
}
// Invoke the callback for each underlying property symbol of the given symbol and return the first
// value that isn't undefined.
function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T | undefined {
if (getCheckFlags(prop) & CheckFlags.Synthetic) {
// NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.Synthetic
for (const t of (prop as TransientSymbol).links.containingType!.types) {
const p = getPropertyOfType(t, prop.escapedName);
const result = p && forEachProperty(p, callback);
if (result) {
return result;
}
}
return undefined;
}
return callback(prop);
}
// Return the declaring class type of a property or undefined if property not declared in class
function getDeclaringClass(prop: Symbol) {
return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) as InterfaceType : undefined;
}
// Return the inherited type of the given property or undefined if property doesn't exist in a base class.
function getTypeOfPropertyInBaseClass(property: Symbol) {
const classType = getDeclaringClass(property);
const baseClassType = classType && getBaseTypes(classType)[0];
return baseClassType && getTypeOfPropertyOfType(baseClassType, property.escapedName);
}
// Return true if some underlying source property is declared in a class that derives
// from the given base class.
function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type | undefined) {
return forEachProperty(prop, sp => {
const sourceClass = getDeclaringClass(sp);
return sourceClass ? hasBaseType(sourceClass, baseClass) : false;
});
}
// Return true if source property is a valid override of protected parts of target property.
function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) {
return !forEachProperty(targetProp, tp =>
getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ?
!isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false);
}
// Return true if the given class derives from each of the declaring classes of the protected
// constituents of the given property.
function isClassDerivedFromDeclaringClasses(checkClass: T, prop: Symbol, writing: boolean) {
return forEachProperty(prop, p =>
getDeclarationModifierFlagsFromSymbol(p, writing) & ModifierFlags.Protected ?
!hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass;
}
// Return true if the given type is deeply nested. We consider this to be the case when the given stack contains
// maxDepth or more occurrences of types with the same recursion identity as the given type. The recursion identity
// provides a shared identity for type instantiations that repeat in some (possibly infinite) pattern. For example,
// in `type Deep = { next: Deep> }`, repeatedly referencing the `next` property leads to an infinite
// sequence of ever deeper instantiations with the same recursion identity (in this case the symbol associated with
// the object type literal).
// A homomorphic mapped type is considered deeply nested if its target type is deeply nested, and an intersection is
// considered deeply nested if any constituent of the intersection is deeply nested.
// It is possible, though highly unlikely, for the deeply nested check to be true in a situation where a chain of
// instantiations is not infinitely expanding. Effectively, we will generate a false positive when two types are
// structurally equal to at least maxDepth levels, but unequal at some level beyond that.
function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean {
if (depth >= maxDepth) {
if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) {
type = getMappedTargetWithSymbol(type);
}
if (type.flags & TypeFlags.Intersection) {
return some((type as IntersectionType).types, t => isDeeplyNestedType(t, stack, depth, maxDepth));
}
const identity = getRecursionIdentity(type);
let count = 0;
let lastTypeId = 0;
for (let i = 0; i < depth; i++) {
const t = stack[i];
if (hasMatchingRecursionIdentity(t, identity)) {
// We only count occurrences with a higher type id than the previous occurrence, since higher
// type ids are an indicator of newer instantiations caused by recursion.
if (t.id >= lastTypeId) {
count++;
if (count >= maxDepth) {
return true;
}
}
lastTypeId = t.id;
}
}
}
return false;
}
// Unwrap nested homomorphic mapped types and return the deepest target type that has a symbol. This better
// preserves unique type identities for mapped types applied to explicitly written object literals. For example
// in `Mapped<{ x: Mapped<{ x: Mapped<{ x: string }>}>}>`, each of the mapped type applications will have a
// unique recursion identity (that of their target object type literal) and thus avoid appearing deeply nested.
function getMappedTargetWithSymbol(type: Type) {
let target;
while (
(getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped &&
(target = getModifiersTypeFromMappedType(type as MappedType)) &&
(target.symbol || target.flags & TypeFlags.Intersection && some((target as IntersectionType).types, t => !!t.symbol))
) {
type = target;
}
return type;
}
function hasMatchingRecursionIdentity(type: Type, identity: object): boolean {
if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) {
type = getMappedTargetWithSymbol(type);
}
if (type.flags & TypeFlags.Intersection) {
return some((type as IntersectionType).types, t => hasMatchingRecursionIdentity(t, identity));
}
return getRecursionIdentity(type) === identity;
}
// The recursion identity of a type is an object identity that is shared among multiple instantiations of the type.
// We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with
// the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all
// instantiations of that type have the same recursion identity. The default recursion identity is the object
// identity of the type, meaning that every type is unique. Generally, types with constituents that could circularly
// reference the type have a recursion identity that differs from the object identity.
function getRecursionIdentity(type: Type): object {
// Object and array literals are known not to contain recursive references and don't need a recursion identity.
if (type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) {
if (getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).node) {
// Deferred type references are tracked through their associated AST node. This gives us finer
// granularity than using their associated target because each manifest type reference has a
// unique AST node.
return (type as TypeReference).node!;
}
if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
// We track object types that have a symbol by that symbol (representing the origin of the type), but
// exclude the static side of a class since it shares its symbol with the instance side.
return type.symbol;
}
if (isTupleType(type)) {
return type.target;
}
}
if (type.flags & TypeFlags.TypeParameter) {
// We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter
// have the same recursion identity.
return type.symbol;
}
if (type.flags & TypeFlags.IndexedAccess) {
// Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] it is A.
do {
type = (type as IndexedAccessType).objectType;
}
while (type.flags & TypeFlags.IndexedAccess);
return type;
}
if (type.flags & TypeFlags.Conditional) {
// The root object represents the origin of the conditional type
return (type as ConditionalType).root;
}
return type;
}
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
}
function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary {
// Two members are considered identical when
// - they are public properties with identical names, optionality, and types,
// - they are private or protected properties originating in the same declaration and having identical types
if (sourceProp === targetProp) {
return Ternary.True;
}
const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier;
const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier;
if (sourcePropAccessibility !== targetPropAccessibility) {
return Ternary.False;
}
if (sourcePropAccessibility) {
if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) {
return Ternary.False;
}
}
else {
if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) {
return Ternary.False;
}
}
if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) {
return Ternary.False;
}
return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp));
}
function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) {
const sourceParameterCount = getParameterCount(source);
const targetParameterCount = getParameterCount(target);
const sourceMinArgumentCount = getMinArgumentCount(source);
const targetMinArgumentCount = getMinArgumentCount(target);
const sourceHasRestParameter = hasEffectiveRestParameter(source);
const targetHasRestParameter = hasEffectiveRestParameter(target);
// A source signature matches a target signature if the two signatures have the same number of required,
// optional, and rest parameters.
if (
sourceParameterCount === targetParameterCount &&
sourceMinArgumentCount === targetMinArgumentCount &&
sourceHasRestParameter === targetHasRestParameter
) {
return true;
}
// A source signature partially matches a target signature if the target signature has no fewer required
// parameters
if (partialMatch && sourceMinArgumentCount <= targetMinArgumentCount) {
return true;
}
return false;
}
/**
* See signatureRelatedTo, compareSignaturesIdentical
*/
function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary {
// TODO (drosen): De-duplicate code between related functions.
if (source === target) {
return Ternary.True;
}
if (!(isMatchingSignature(source, target, partialMatch))) {
return Ternary.False;
}
// Check that the two signatures have the same number of type parameters.
if (length(source.typeParameters) !== length(target.typeParameters)) {
return Ternary.False;
}
// Check that type parameter constraints and defaults match. If they do, instantiate the source
// signature with the type parameters of the target signature and continue the comparison.
if (target.typeParameters) {
const mapper = createTypeMapper(source.typeParameters!, target.typeParameters);
for (let i = 0; i < target.typeParameters.length; i++) {
const s = source.typeParameters![i];
const t = target.typeParameters[i];
if (
!(s === t || compareTypes(instantiateType(getConstraintFromTypeParameter(s), mapper) || unknownType, getConstraintFromTypeParameter(t) || unknownType) &&
compareTypes(instantiateType(getDefaultFromTypeParameter(s), mapper) || unknownType, getDefaultFromTypeParameter(t) || unknownType))
) {
return Ternary.False;
}
}
source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true);
}
let result = Ternary.True;
if (!ignoreThisTypes) {
const sourceThisType = getThisTypeOfSignature(source);
if (sourceThisType) {
const targetThisType = getThisTypeOfSignature(target);
if (targetThisType) {
const related = compareTypes(sourceThisType, targetThisType);
if (!related) {
return Ternary.False;
}
result &= related;
}
}
}
const targetLen = getParameterCount(target);
for (let i = 0; i < targetLen; i++) {
const s = getTypeAtPosition(source, i);
const t = getTypeAtPosition(target, i);
const related = compareTypes(t, s);
if (!related) {
return Ternary.False;
}
result &= related;
}
if (!ignoreReturnTypes) {
const sourceTypePredicate = getTypePredicateOfSignature(source);
const targetTypePredicate = getTypePredicateOfSignature(target);
result &= sourceTypePredicate || targetTypePredicate ?
compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) :
compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
}
return result;
}
function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary {
return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False :
source.type === target.type ? Ternary.True :
source.type && target.type ? compareTypes(source.type, target.type) :
Ternary.False;
}
function literalTypesWithSameBaseType(types: Type[]): boolean {
let commonBaseType: Type | undefined;
for (const t of types) {
if (!(t.flags & TypeFlags.Never)) {
const baseType = getBaseTypeOfLiteralType(t);
commonBaseType ??= baseType;
if (baseType === t || baseType !== commonBaseType) {
return false;
}
}
}
return true;
}
function getCombinedTypeFlags(types: Type[]): TypeFlags {
return reduceLeft(types, (flags, t) => flags | (t.flags & TypeFlags.Union ? getCombinedTypeFlags((t as UnionType).types) : t.flags), 0 as TypeFlags);
}
function getCommonSupertype(types: Type[]): Type {
if (types.length === 1) {
return types[0];
}
// Remove nullable types from each of the candidates.
const primaryTypes = strictNullChecks ? sameMap(types, t => filterType(t, u => !(u.flags & TypeFlags.Nullable))) : types;
// When the candidate types are all literal types with the same base type, return a union
// of those literal types. Otherwise, return the leftmost type for which no type to the
// right is a supertype.
const superTypeOrUnion = literalTypesWithSameBaseType(primaryTypes) ?
getUnionType(primaryTypes) :
getSingleCommonSupertype(primaryTypes);
// Add any nullable types that occurred in the candidates back to the result.
return primaryTypes === types ? superTypeOrUnion : getNullableType(superTypeOrUnion, getCombinedTypeFlags(types) & TypeFlags.Nullable);
}
function getSingleCommonSupertype(types: Type[]) {
// First, find the leftmost type for which no type to the right is a strict supertype, and if that
// type is a strict supertype of all other candidates, return it. Otherwise, return the leftmost type
// for which no type to the right is a (regular) supertype.
const candidate = reduceLeft(types, (s, t) => isTypeStrictSubtypeOf(s, t) ? t : s)!;
return every(types, t => t === candidate || isTypeStrictSubtypeOf(t, candidate)) ?
candidate :
reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!;
}
// Return the leftmost type for which no type to the right is a subtype.
function getCommonSubtype(types: Type[]) {
return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!;
}
function isArrayType(type: Type): type is TypeReference {
return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type as TypeReference).target === globalArrayType || (type as TypeReference).target === globalReadonlyArrayType);
}
function isReadonlyArrayType(type: Type): boolean {
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type as TypeReference).target === globalReadonlyArrayType;
}
function isArrayOrTupleType(type: Type): type is TypeReference {
return isArrayType(type) || isTupleType(type);
}
function isMutableArrayOrTuple(type: Type): boolean {
return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly;
}
function getElementTypeOfArrayType(type: Type): Type | undefined {
return isArrayType(type) ? getTypeArguments(type)[0] : undefined;
}
function isArrayLikeType(type: Type): boolean {
// A type is array-like if it is a reference to the global Array or global ReadonlyArray type,
// or if it is not the undefined or null type and if it is assignable to ReadonlyArray
return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
}
function isMutableArrayLikeType(type: Type): boolean {
// A type is mutable-array-like if it is a reference to the global Array type, or if it is not the
// any, undefined or null type and if it is assignable to Array
return isMutableArrayOrTuple(type) || !(type.flags & (TypeFlags.Any | TypeFlags.Nullable)) && isTypeAssignableTo(type, anyArrayType);
}
function getSingleBaseForNonAugmentingSubtype(type: Type) {
if (!(getObjectFlags(type) & ObjectFlags.Reference) || !(getObjectFlags((type as TypeReference).target) & ObjectFlags.ClassOrInterface)) {
return undefined;
}
if (getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeCalculated) {
return getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeExists ? (type as TypeReference).cachedEquivalentBaseType : undefined;
}
(type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeCalculated;
const target = (type as TypeReference).target as InterfaceType;
if (getObjectFlags(target) & ObjectFlags.Class) {
const baseTypeNode = getBaseTypeNodeOfClass(target);
// A base type expression may circularly reference the class itself (e.g. as an argument to function call), so we only
// check for base types specified as simple qualified names.
if (baseTypeNode && baseTypeNode.expression.kind !== SyntaxKind.Identifier && baseTypeNode.expression.kind !== SyntaxKind.PropertyAccessExpression) {
return undefined;
}
}
const bases = getBaseTypes(target);
if (bases.length !== 1) {
return undefined;
}
if (getMembersOfSymbol(type.symbol).size) {
return undefined; // If the interface has any members, they may subtype members in the base, so we should do a full structural comparison
}
let instantiatedBase = !length(target.typeParameters) ? bases[0] : instantiateType(bases[0], createTypeMapper(target.typeParameters!, getTypeArguments(type as TypeReference).slice(0, target.typeParameters!.length)));
if (length(getTypeArguments(type as TypeReference)) > length(target.typeParameters)) {
instantiatedBase = getTypeWithThisArgument(instantiatedBase, last(getTypeArguments(type as TypeReference)));
}
(type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeExists;
return (type as TypeReference).cachedEquivalentBaseType = instantiatedBase;
}
function isEmptyLiteralType(type: Type): boolean {
return strictNullChecks ? type === implicitNeverType : type === undefinedWideningType;
}
function isEmptyArrayLiteralType(type: Type): boolean {
const elementType = getElementTypeOfArrayType(type);
return !!elementType && isEmptyLiteralType(elementType);
}
function isTupleLikeType(type: Type): boolean {
let lengthType;
return isTupleType(type) ||
!!getPropertyOfType(type, "0" as __String) ||
isArrayLikeType(type) && !!(lengthType = getTypeOfPropertyOfType(type, "length" as __String)) && everyType(lengthType, t => !!(t.flags & TypeFlags.NumberLiteral));
}
function isArrayOrTupleLikeType(type: Type): boolean {
return isArrayLikeType(type) || isTupleLikeType(type);
}
function getTupleElementType(type: Type, index: number) {
const propType = getTypeOfPropertyOfType(type, "" + index as __String);
if (propType) {
return propType;
}
if (everyType(type, isTupleType)) {
return getTupleElementTypeOutOfStartCount(type, index, compilerOptions.noUncheckedIndexedAccess ? undefinedType : undefined);
}
return undefined;
}
function isNeitherUnitTypeNorNever(type: Type): boolean {
return !(type.flags & (TypeFlags.Unit | TypeFlags.Never));
}
function isUnitType(type: Type): boolean {
return !!(type.flags & TypeFlags.Unit);
}
function isUnitLikeType(type: Type): boolean {
// Intersections that reduce to 'never' (e.g. 'T & null' where 'T extends {}') are not unit types.
const t = getBaseConstraintOrType(type);
// Scan intersections such that tagged literal types are considered unit types.
return t.flags & TypeFlags.Intersection ? some((t as IntersectionType).types, isUnitType) : isUnitType(t);
}
function extractUnitType(type: Type) {
return type.flags & TypeFlags.Intersection ? find((type as IntersectionType).types, isUnitType) || type : type;
}
function isLiteralType(type: Type): boolean {
return type.flags & TypeFlags.Boolean ? true :
type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type as UnionType).types, isUnitType) :
isUnitType(type);
}
function getBaseTypeOfLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLike ? getBaseTypeOfEnumLikeType(type as LiteralType) :
type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType :
type.flags & TypeFlags.NumberLiteral ? numberType :
type.flags & TypeFlags.BigIntLiteral ? bigintType :
type.flags & TypeFlags.BooleanLiteral ? booleanType :
type.flags & TypeFlags.Union ? getBaseTypeOfLiteralTypeUnion(type as UnionType) :
type;
}
function getBaseTypeOfLiteralTypeUnion(type: UnionType) {
const key = `B${getTypeId(type)}`;
return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType));
}
// This like getBaseTypeOfLiteralType, but instead treats enum literals as strings/numbers instead
// of returning their enum base type (which depends on the types of other literals in the enum).
function getBaseTypeOfLiteralTypeForComparison(type: Type): Type {
return type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType :
type.flags & (TypeFlags.NumberLiteral | TypeFlags.Enum) ? numberType :
type.flags & TypeFlags.BigIntLiteral ? bigintType :
type.flags & TypeFlags.BooleanLiteral ? booleanType :
type.flags & TypeFlags.Union ? mapType(type, getBaseTypeOfLiteralTypeForComparison) :
type;
}
function getWidenedLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLike && isFreshLiteralType(type) ? getBaseTypeOfEnumLikeType(type as LiteralType) :
type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType :
type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType :
type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType :
type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType :
type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedLiteralType) :
type;
}
function getWidenedUniqueESSymbolType(type: Type): Type {
return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType :
type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedUniqueESSymbolType) :
type;
}
function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) {
if (!isLiteralOfContextualType(type, contextualType)) {
type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type));
}
return getRegularTypeOfLiteralType(type);
}
function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) {
if (type && isUnitType(type)) {
const contextualType = !contextualSignatureReturnType ? undefined :
isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) :
contextualSignatureReturnType;
type = getWidenedLiteralLikeTypeForContextualType(type, contextualType);
}
return type;
}
function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) {
if (type && isUnitType(type)) {
const contextualType = !contextualSignatureReturnType ? undefined :
getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator);
type = getWidenedLiteralLikeTypeForContextualType(type, contextualType);
}
return type;
}
/**
* Check if a Type was written as a tuple type literal.
* Prefer using isTupleLikeType() unless the use of `elementTypes`/`getTypeArguments` is required.
*/
function isTupleType(type: Type): type is TupleTypeReference {
return !!(getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target.objectFlags & ObjectFlags.Tuple);
}
function isGenericTupleType(type: Type): type is TupleTypeReference {
return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic);
}
function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference {
return isGenericTupleType(type) && type.target.elementFlags.length === 1;
}
function getRestTypeOfTupleType(type: TupleTypeReference) {
return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength);
}
function getTupleElementTypeOutOfStartCount(type: Type, index: number, undefinedOrMissingType: Type | undefined) {
return mapType(type, t => {
const tupleType = t as TupleTypeReference;
const restType = getRestTypeOfTupleType(tupleType);
if (!restType) {
return undefinedType;
}
if (undefinedOrMissingType && index >= getTotalFixedElementCount(tupleType.target)) {
return getUnionType([restType, undefinedOrMissingType]);
}
return restType;
});
}
function getRestArrayTypeOfTupleType(type: TupleTypeReference) {
const restType = getRestTypeOfTupleType(type);
return restType && createArrayType(restType);
}
function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false, noReductions = false) {
const length = getTypeReferenceArity(type) - endSkipCount;
if (index < length) {
const typeArguments = getTypeArguments(type);
const elementTypes: Type[] = [];
for (let i = index; i < length; i++) {
const t = typeArguments[i];
elementTypes.push(type.target.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t);
}
return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes, noReductions ? UnionReduction.None : UnionReduction.Literal);
}
return undefined;
}
function isTupleTypeStructureMatching(t1: TupleTypeReference, t2: TupleTypeReference) {
return getTypeReferenceArity(t1) === getTypeReferenceArity(t2) &&
every(t1.target.elementFlags, (f, i) => (f & ElementFlags.Variable) === (t2.target.elementFlags[i] & ElementFlags.Variable));
}
function isZeroBigInt({ value }: BigIntLiteralType) {
return value.base10Value === "0";
}
function removeDefinitelyFalsyTypes(type: Type): Type {
return filterType(type, t => hasTypeFacts(t, TypeFacts.Truthy));
}
function extractDefinitelyFalsyTypes(type: Type): Type {
return mapType(type, getDefinitelyFalsyPartOfType);
}
function getDefinitelyFalsyPartOfType(type: Type): Type {
return type.flags & TypeFlags.String ? emptyStringType :
type.flags & TypeFlags.Number ? zeroType :
type.flags & TypeFlags.BigInt ? zeroBigIntType :
type === regularFalseType ||
type === falseType ||
type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null | TypeFlags.AnyOrUnknown) ||
type.flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === "" ||
type.flags & TypeFlags.NumberLiteral && (type as NumberLiteralType).value === 0 ||
type.flags & TypeFlags.BigIntLiteral && isZeroBigInt(type as BigIntLiteralType) ? type :
neverType;
}
/**
* Add undefined or null or both to a type if they are missing.
* @param type - type to add undefined and/or null to if not present
* @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both
*/
function getNullableType(type: Type, flags: TypeFlags): Type {
const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null);
return missing === 0 ? type :
missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) :
missing === TypeFlags.Null ? getUnionType([type, nullType]) :
getUnionType([type, undefinedType, nullType]);
}
function getOptionalType(type: Type, isProperty = false): Type {
Debug.assert(strictNullChecks);
const missingOrUndefined = isProperty ? undefinedOrMissingType : undefinedType;
return type === missingOrUndefined || type.flags & TypeFlags.Union && (type as UnionType).types[0] === missingOrUndefined ? type : getUnionType([type, missingOrUndefined]);
}
function getGlobalNonNullableTypeInstantiation(type: Type) {
if (!deferredGlobalNonNullableTypeAlias) {
deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol;
}
return deferredGlobalNonNullableTypeAlias !== unknownSymbol ?
getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]) :
getIntersectionType([type, emptyObjectType]);
}
function getNonNullableType(type: Type): Type {
return strictNullChecks ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
}
function addOptionalTypeMarker(type: Type) {
return strictNullChecks ? getUnionType([type, optionalType]) : type;
}
function removeOptionalTypeMarker(type: Type): Type {
return strictNullChecks ? removeType(type, optionalType) : type;
}
function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) {
return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type;
}
function getOptionalExpressionType(exprType: Type, expression: Expression) {
return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) :
isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) :
exprType;
}
function removeMissingType(type: Type, isOptional: boolean) {
return exactOptionalPropertyTypes && isOptional ? removeType(type, missingType) : type;
}
function containsMissingType(type: Type) {
return type === missingType || !!(type.flags & TypeFlags.Union) && (type as UnionType).types[0] === missingType;
}
function removeMissingOrUndefinedType(type: Type): Type {
return exactOptionalPropertyTypes ? removeType(type, missingType) : getTypeWithFacts(type, TypeFacts.NEUndefined);
}
/**
* Is source potentially coercible to target type under `==`.
* Assumes that `source` is a constituent of a union, hence
* the boolean literal flag on the LHS, but not on the RHS.
*
* This does not fully replicate the semantics of `==`. The
* intention is to catch cases that are clearly not right.
*
* Comparing (string | number) to number should not remove the
* string element.
*
* Comparing (string | number) to 1 will remove the string
* element, though this is not sound. This is a pragmatic
* choice.
*
* @see narrowTypeByEquality
*
* @param source
* @param target
*/
function isCoercibleUnderDoubleEquals(source: Type, target: Type): boolean {
return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0)
&& ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0);
}
/**
* Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module
* with no call or construct signatures.
*/
function isObjectTypeWithInferableIndex(type: Type): boolean {
const objectFlags = getObjectFlags(type);
return type.flags & TypeFlags.Intersection
? every((type as IntersectionType).types, isObjectTypeWithInferableIndex)
: !!(
type.symbol
&& (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0
&& !(type.symbol.flags & SymbolFlags.Class)
&& !typeHasCallOrConstructSignatures(type)
) || !!(
objectFlags & ObjectFlags.ObjectRestType
) || !!(objectFlags & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source));
}
function createSymbolWithType(source: Symbol, type: Type | undefined) {
const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly);
symbol.declarations = source.declarations;
symbol.parent = source.parent;
symbol.links.type = type;
symbol.links.target = source;
if (source.valueDeclaration) {
symbol.valueDeclaration = source.valueDeclaration;
}
const nameType = getSymbolLinks(source).nameType;
if (nameType) {
symbol.links.nameType = nameType;
}
return symbol;
}
function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) {
const members = createSymbolTable();
for (const property of getPropertiesOfObjectType(type)) {
const original = getTypeOfSymbol(property);
const updated = f(original);
members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated));
}
return members;
}
/**
* If the the provided object literal is subject to the excess properties check,
* create a new that is exempt. Recursively mark object literal members as exempt.
* Leave signatures alone since they are not subject to the check.
*/
function getRegularTypeOfObjectLiteral(type: Type): Type {
if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) {
return type;
}
const regularType = (type as FreshObjectLiteralType).regularType;
if (regularType) {
return regularType;
}
const resolved = type as ResolvedType;
const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral);
const regularNew = createAnonymousType(resolved.symbol, members, resolved.callSignatures, resolved.constructSignatures, resolved.indexInfos);
regularNew.flags = resolved.flags;
regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral;
(type as FreshObjectLiteralType).regularType = regularNew;
return regularNew;
}
function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext {
return { parent, propertyName, siblings, resolvedProperties: undefined };
}
function getSiblingsOfContext(context: WideningContext): Type[] {
if (!context.siblings) {
const siblings: Type[] = [];
for (const type of getSiblingsOfContext(context.parent!)) {
if (isObjectLiteralType(type)) {
const prop = getPropertyOfObjectType(type, context.propertyName!);
if (prop) {
forEachType(getTypeOfSymbol(prop), t => {
siblings.push(t);
});
}
}
}
context.siblings = siblings;
}
return context.siblings;
}
function getPropertiesOfContext(context: WideningContext): Symbol[] {
if (!context.resolvedProperties) {
const names = new Map<__String, Symbol>();
for (const t of getSiblingsOfContext(context)) {
if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) {
for (const prop of getPropertiesOfType(t)) {
names.set(prop.escapedName, prop);
}
}
}
context.resolvedProperties = arrayFrom(names.values());
}
return context.resolvedProperties;
}
function getWidenedProperty(prop: Symbol, context: WideningContext | undefined): Symbol {
if (!(prop.flags & SymbolFlags.Property)) {
// Since get accessors already widen their return value there is no need to
// widen accessor based properties here.
return prop;
}
const original = getTypeOfSymbol(prop);
const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined);
const widened = getWidenedTypeWithContext(original, propContext);
return widened === original ? prop : createSymbolWithType(prop, widened);
}
function getUndefinedProperty(prop: Symbol) {
const cached = undefinedProperties.get(prop.escapedName);
if (cached) {
return cached;
}
const result = createSymbolWithType(prop, undefinedOrMissingType);
result.flags |= SymbolFlags.Optional;
undefinedProperties.set(prop.escapedName, result);
return result;
}
function getWidenedTypeOfObjectLiteral(type: Type, context: WideningContext | undefined): Type {
const members = createSymbolTable();
for (const prop of getPropertiesOfObjectType(type)) {
members.set(prop.escapedName, getWidenedProperty(prop, context));
}
if (context) {
for (const prop of getPropertiesOfContext(context)) {
if (!members.has(prop.escapedName)) {
members.set(prop.escapedName, getUndefinedProperty(prop));
}
}
}
const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly, info.declaration, info.components)));
result.objectFlags |= getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType); // Retain js literal flag through widening
return result;
}
function getWidenedType(type: Type) {
return getWidenedTypeWithContext(type, /*context*/ undefined);
}
function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type {
if (getObjectFlags(type) & ObjectFlags.RequiresWidening) {
if (context === undefined && type.widened) {
return type.widened;
}
let result: Type | undefined;
if (type.flags & (TypeFlags.Any | TypeFlags.Nullable)) {
result = anyType;
}
else if (isObjectLiteralType(type)) {
result = getWidenedTypeOfObjectLiteral(type, context);
}
else if (type.flags & TypeFlags.Union) {
const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type as UnionType).types);
const widenedTypes = sameMap((type as UnionType).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext));
// Widening an empty object literal transitions from a highly restrictive type to
// a highly inclusive one. For that reason we perform subtype reduction here if the
// union includes empty object types (e.g. reducing {} | string to just {}).
result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal);
}
else if (type.flags & TypeFlags.Intersection) {
result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType));
}
else if (isArrayOrTupleType(type)) {
result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType));
}
if (result && context === undefined) {
type.widened = result;
}
return result || type;
}
return type;
}
/**
* Reports implicit any errors that occur as a result of widening 'null' and 'undefined'
* to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to
* getWidenedType. But in some cases getWidenedType is called without reporting errors
* (type argument inference is an example).
*
* The return value indicates whether an error was in fact reported. The particular circumstances
* are on a best effort basis. Currently, if the null or undefined that causes widening is inside
* an object literal property (arbitrarily deeply), this function reports an error. If no error is
* reported, reportImplicitAnyError is a suitable fallback to report a general error.
*/
function reportWideningErrorsInType(type: Type): boolean {
let errorReported = false;
if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) {
if (type.flags & TypeFlags.Union) {
if (some((type as UnionType).types, isEmptyObjectType)) {
errorReported = true;
}
else {
for (const t of (type as UnionType).types) {
errorReported ||= reportWideningErrorsInType(t);
}
}
}
else if (isArrayOrTupleType(type)) {
for (const t of getTypeArguments(type)) {
errorReported ||= reportWideningErrorsInType(t);
}
}
else if (isObjectLiteralType(type)) {
for (const p of getPropertiesOfObjectType(type)) {
const t = getTypeOfSymbol(p);
if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) {
errorReported = reportWideningErrorsInType(t);
if (!errorReported) {
// we need to account for property types coming from object literal type normalization in unions
const valueDeclaration = p.declarations?.find(d => d.symbol.valueDeclaration?.parent === type.symbol.valueDeclaration);
if (valueDeclaration) {
error(valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t)));
errorReported = true;
}
}
}
}
}
}
return errorReported;
}
function reportImplicitAny(declaration: Declaration, type: Type, wideningKind?: WideningKind) {
const typeAsString = typeToString(getWidenedType(type));
if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) {
// Only report implicit any errors/suggestions in TS and ts-check JS files
return;
}
let diagnostic: DiagnosticMessage;
switch (declaration.kind) {
case SyntaxKind.BinaryExpression:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage;
break;
case SyntaxKind.Parameter:
const param = declaration as ParameterDeclaration;
if (isIdentifier(param.name)) {
const originalKeywordKind = identifierToKeywordKind(param.name);
if (
(isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) &&
param.parent.parameters.includes(param) &&
(resolveName(param, param.name.escapedText, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*isUse*/ true) ||
originalKeywordKind && isTypeNodeKind(originalKeywordKind))
) {
const newName = "arg" + param.parent.parameters.indexOf(param);
const typeName = declarationNameToString(param.name) + (param.dotDotDotToken ? "[]" : "");
errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, typeName);
return;
}
}
diagnostic = (declaration as ParameterDeclaration).dotDotDotToken ?
noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage :
noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage;
break;
case SyntaxKind.BindingElement:
diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type;
if (!noImplicitAny) {
// Don't issue a suggestion for binding elements since the codefix doesn't yet support them.
return;
}
break;
case SyntaxKind.JSDocFunctionType:
error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString);
return;
case SyntaxKind.JSDocSignature:
if (noImplicitAny && isJSDocOverloadTag(declaration.parent)) {
error(declaration.parent.tagName, Diagnostics.This_overload_implicitly_returns_the_type_0_because_it_lacks_a_return_type_annotation, typeAsString);
}
return;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
if (noImplicitAny && !(declaration as NamedDeclaration).name) {
if (wideningKind === WideningKind.GeneratorYield) {
error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_Consider_supplying_a_return_type_annotation, typeAsString);
}
else {
error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString);
}
return;
}
diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage :
wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type :
Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type;
break;
case SyntaxKind.MappedType:
if (noImplicitAny) {
error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type);
}
return;
default:
diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage;
}
errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString);
}
function shouldReportErrorsFromWideningWithContextualSignature(declaration: FunctionLikeDeclaration, wideningKind: WideningKind) {
const signature = getContextualSignatureForFunctionLikeDeclaration(declaration);
if (!signature) {
return true;
}
let returnType = getReturnTypeOfSignature(signature);
const flags = getFunctionFlags(declaration);
switch (wideningKind) {
case WideningKind.FunctionReturn:
if (flags & FunctionFlags.Generator) {
returnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, !!(flags & FunctionFlags.Async)) ?? returnType;
}
else if (flags & FunctionFlags.Async) {
returnType = getAwaitedTypeNoAlias(returnType) ?? returnType;
}
return isGenericType(returnType);
case WideningKind.GeneratorYield:
const yieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, !!(flags & FunctionFlags.Async));
return !!yieldType && isGenericType(yieldType);
case WideningKind.GeneratorNext:
const nextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, !!(flags & FunctionFlags.Async));
return !!nextType && isGenericType(nextType);
}
return false;
}
function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) {
addLazyDiagnostic(() => {
if (noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType) {
if (!wideningKind || isFunctionLikeDeclaration(declaration) && shouldReportErrorsFromWideningWithContextualSignature(declaration, wideningKind)) {
// Report implicit any error within type if possible, otherwise report error on declaration
if (!reportWideningErrorsInType(type)) {
reportImplicitAny(declaration, type, wideningKind);
}
}
}
});
}
function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) {
const sourceCount = getParameterCount(source);
const targetCount = getParameterCount(target);
const sourceRestType = getEffectiveRestType(source);
const targetRestType = getEffectiveRestType(target);
const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount;
const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount);
const sourceThisType = getThisTypeOfSignature(source);
if (sourceThisType) {
const targetThisType = getThisTypeOfSignature(target);
if (targetThisType) {
callback(sourceThisType, targetThisType);
}
}
for (let i = 0; i < paramCount; i++) {
callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i));
}
if (targetRestType) {
callback(getRestTypeAtPosition(source, paramCount, /*readonly*/ isConstTypeVariable(targetRestType) && !someType(targetRestType, isMutableArrayLikeType)), targetRestType);
}
}
function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) {
const targetTypePredicate = getTypePredicateOfSignature(target);
if (targetTypePredicate) {
const sourceTypePredicate = getTypePredicateOfSignature(source);
if (sourceTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) {
callback(sourceTypePredicate.type, targetTypePredicate.type);
return;
}
}
const targetReturnType = getReturnTypeOfSignature(target);
if (couldContainTypeVariables(targetReturnType)) {
callback(getReturnTypeOfSignature(source), targetReturnType);
}
}
function createInferenceContext(typeParameters: readonly TypeParameter[], signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext {
return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable);
}
function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined {
return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes);
}
function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext {
const context: InferenceContext = {
inferences,
signature,
flags,
compareTypes,
mapper: reportUnmeasurableMapper, // initialize to a noop mapper so the context object is available, but the underlying object shape is right upon construction
nonFixingMapper: reportUnmeasurableMapper,
};
context.mapper = makeFixingMapperForContext(context);
context.nonFixingMapper = makeNonFixingMapperForContext(context);
return context;
}
function makeFixingMapperForContext(context: InferenceContext) {
return makeDeferredTypeMapper(
map(context.inferences, i => i.typeParameter),
map(context.inferences, (inference, i) => () => {
if (!inference.isFixed) {
// Before we commit to a particular inference (and thus lock out any further inferences),
// we infer from any intra-expression inference sites we have collected.
inferFromIntraExpressionSites(context);
clearCachedInferences(context.inferences);
inference.isFixed = true;
}
return getInferredType(context, i);
}),
);
}
function makeNonFixingMapperForContext(context: InferenceContext) {
return makeDeferredTypeMapper(
map(context.inferences, i => i.typeParameter),
map(context.inferences, (_, i) => () => {
return getInferredType(context, i);
}),
);
}
function clearCachedInferences(inferences: InferenceInfo[]) {
for (const inference of inferences) {
if (!inference.isFixed) {
inference.inferredType = undefined;
}
}
}
function addIntraExpressionInferenceSite(context: InferenceContext, node: Expression | MethodDeclaration, type: Type) {
(context.intraExpressionInferenceSites ??= []).push({ node, type });
}
// We collect intra-expression inference sites within object and array literals to handle cases where
// inferred types flow between context sensitive element expressions. For example:
//
// declare function foo(arg: [(n: number) => T, (x: T) => void]): void;
// foo([_a => 0, n => n.toFixed()]);
//
// Above, both arrow functions in the tuple argument are context sensitive, thus both are omitted from the
// pass that collects inferences from the non-context sensitive parts of the arguments. In the subsequent
// pass where nothing is omitted, we need to commit to an inference for T in order to contextually type the
// parameter in the second arrow function, but we want to first infer from the return type of the first
// arrow function. This happens automatically when the arrow functions are discrete arguments (because we
// infer from each argument before processing the next), but when the arrow functions are elements of an
// object or array literal, we need to perform intra-expression inferences early.
function inferFromIntraExpressionSites(context: InferenceContext) {
if (context.intraExpressionInferenceSites) {
for (const { node, type } of context.intraExpressionInferenceSites) {
const contextualType = node.kind === SyntaxKind.MethodDeclaration ?
getContextualTypeForObjectLiteralMethod(node as MethodDeclaration, ContextFlags.NoConstraints) :
getContextualType(node, ContextFlags.NoConstraints);
if (contextualType) {
inferTypes(context.inferences, type, contextualType);
}
}
context.intraExpressionInferenceSites = undefined;
}
}
function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo {
return {
typeParameter,
candidates: undefined,
contraCandidates: undefined,
inferredType: undefined,
priority: undefined,
topLevel: true,
isFixed: false,
impliedArity: undefined,
};
}
function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo {
return {
typeParameter: inference.typeParameter,
candidates: inference.candidates && inference.candidates.slice(),
contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(),
inferredType: inference.inferredType,
priority: inference.priority,
topLevel: inference.topLevel,
isFixed: inference.isFixed,
impliedArity: inference.impliedArity,
};
}
function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined {
const inferences = filter(context.inferences, hasInferenceCandidates);
return inferences.length ?
createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) :
undefined;
}
function getMapperFromContext(context: T): TypeMapper | T & undefined {
return context && context.mapper;
}
// Return true if the given type could possibly reference a type parameter for which
// we perform type inference (i.e. a type parameter of a generic function). We cache
// results for union and intersection types for performance reasons.
function couldContainTypeVariables(type: Type): boolean {
const objectFlags = getObjectFlags(type);
if (objectFlags & ObjectFlags.CouldContainTypeVariablesComputed) {
return !!(objectFlags & ObjectFlags.CouldContainTypeVariables);
}
const result = !!(type.flags & TypeFlags.Instantiable ||
type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && (
objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || some(getTypeArguments(type as TypeReference), couldContainTypeVariables)) ||
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ||
objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType)
) ||
type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables));
if (type.flags & TypeFlags.ObjectFlagsType) {
(type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0);
}
return result;
}
function isNonGenericTopLevelType(type: Type) {
if (type.aliasSymbol && !type.aliasTypeArguments) {
const declaration = getDeclarationOfKind(type.aliasSymbol, SyntaxKind.TypeAliasDeclaration);
return !!(declaration && findAncestor(declaration.parent, n => n.kind === SyntaxKind.SourceFile ? true : n.kind === SyntaxKind.ModuleDeclaration ? false : "quit"));
}
return false;
}
function isTypeParameterAtTopLevel(type: Type, tp: TypeParameter, depth = 0): boolean {
return !!(type === tp ||
type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, tp, depth)) ||
depth < 3 && type.flags & TypeFlags.Conditional && (
isTypeParameterAtTopLevel(getTrueTypeFromConditionalType(type as ConditionalType), tp, depth + 1) ||
isTypeParameterAtTopLevel(getFalseTypeFromConditionalType(type as ConditionalType), tp, depth + 1)
));
}
function isTypeParameterAtTopLevelInReturnType(signature: Signature, typeParameter: TypeParameter) {
const typePredicate = getTypePredicateOfSignature(signature);
return typePredicate ? !!typePredicate.type && isTypeParameterAtTopLevel(typePredicate.type, typeParameter) :
isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), typeParameter);
}
/** Create an object with properties named in the string literal type. Every property has type `any` */
function createEmptyObjectTypeFromStringLiteral(type: Type) {
const members = createSymbolTable();
forEachType(type, t => {
if (!(t.flags & TypeFlags.StringLiteral)) {
return;
}
const name = escapeLeadingUnderscores((t as StringLiteralType).value);
const literalProp = createSymbol(SymbolFlags.Property, name);
literalProp.links.type = anyType;
if (t.symbol) {
literalProp.declarations = t.symbol.declarations;
literalProp.valueDeclaration = t.symbol.valueDeclaration;
}
members.set(name, literalProp);
});
const indexInfos = type.flags & TypeFlags.String ? [createIndexInfo(stringType, emptyObjectType, /*isReadonly*/ false)] : emptyArray;
return createAnonymousType(/*symbol*/ undefined, members, emptyArray, emptyArray, indexInfos);
}
/**
* Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct
* an object type with the same set of properties as the source type, where the type of each
* property is computed by inferring from the source property type to X for the type
* variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for).
*/
function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined {
const cacheKey = source.id + "," + target.id + "," + constraint.id;
if (reverseHomomorphicMappedCache.has(cacheKey)) {
return reverseHomomorphicMappedCache.get(cacheKey);
}
const type = createReverseMappedType(source, target, constraint);
reverseHomomorphicMappedCache.set(cacheKey, type);
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))) ||
isTupleType(type) && some(getElementTypes(type), isPartiallyInferableType);
}
function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) {
// We consider a source type reverse mappable if it has a string index signature or if
// it has one or more properties and is of a partially inferable type.
if (!(getIndexInfoOfType(source, stringType) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) {
return undefined;
}
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
// applied to the element type(s).
if (isArrayType(source)) {
const elementType = inferReverseMappedType(getTypeArguments(source)[0], target, constraint);
if (!elementType) {
return undefined;
}
return createArrayType(elementType, isReadonlyArrayType(source));
}
if (isTupleType(source)) {
const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint));
if (!every(elementTypes, (t): t is Type => !!t)) {
return undefined;
}
const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) :
source.target.elementFlags;
return createTupleType(elementTypes, elementFlags, source.target.readonly, source.target.labeledElementDeclarations);
}
// For all other object types we infer a new object type where the reverse mapping has been
// applied to the type of each property.
const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
reversed.source = source;
reversed.mappedType = target;
reversed.constraintType = constraint;
return reversed;
}
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType) || unknownType;
}
return links.type;
}
function inferReverseMappedTypeWorker(sourceType: Type, target: MappedType, constraint: IndexType): Type {
const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)) as TypeParameter;
const templateType = getTemplateTypeFromMappedType(target);
const inference = createInferenceInfo(typeParameter);
inferTypes([inference], sourceType, templateType);
return getTypeFromInference(inference) || unknownType;
}
function inferReverseMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined {
const cacheKey = source.id + "," + target.id + "," + constraint.id;
if (reverseMappedCache.has(cacheKey)) {
return reverseMappedCache.get(cacheKey) || unknownType;
}
reverseMappedSourceStack.push(source);
reverseMappedTargetStack.push(target);
const saveExpandingFlags = reverseExpandingFlags;
if (isDeeplyNestedType(source, reverseMappedSourceStack, reverseMappedSourceStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Source;
if (isDeeplyNestedType(target, reverseMappedTargetStack, reverseMappedTargetStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Target;
let type;
if (reverseExpandingFlags !== ExpandingFlags.Both) {
type = inferReverseMappedTypeWorker(source, target, constraint);
}
reverseMappedSourceStack.pop();
reverseMappedTargetStack.pop();
reverseExpandingFlags = saveExpandingFlags;
reverseMappedCache.set(cacheKey, type);
return type;
}
function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator {
const properties = getPropertiesOfType(target);
for (const targetProp of properties) {
// TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass
if (isStaticPrivateIdentifierProperty(targetProp)) {
continue;
}
if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) {
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
if (!sourceProp) {
yield targetProp;
}
else if (matchDiscriminantProperties) {
const targetType = getTypeOfSymbol(targetProp);
if (targetType.flags & TypeFlags.Unit) {
const sourceType = getTypeOfSymbol(sourceProp);
if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) {
yield targetProp;
}
}
}
}
}
}
function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined {
return firstOrUndefinedIterator(getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties));
}
function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) {
return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength ||
!(target.target.combinedFlags & ElementFlags.Variable) && (!!(source.target.combinedFlags & ElementFlags.Variable) || target.target.fixedLength < source.target.fixedLength);
}
function typesDefinitelyUnrelated(source: Type, target: Type) {
// Two tuple types with incompatible arities are definitely unrelated.
// Two object types that each have a property that is unmatched in the other are definitely unrelated.
return isTupleType(source) && isTupleType(target) ? tupleTypesDefinitelyUnrelated(source, target) :
!!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) &&
!!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ false);
}
function getTypeFromInference(inference: InferenceInfo) {
return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) :
inference.contraCandidates ? getIntersectionType(inference.contraCandidates) :
undefined;
}
function hasSkipDirectInferenceFlag(node: Node) {
return !!getNodeLinks(node).skipDirectInference;
}
function isFromInferenceBlockedSource(type: Type) {
return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag));
}
function templateLiteralTypesDefinitelyUnrelated(source: TemplateLiteralType, target: TemplateLiteralType) {
// Two template literal types with diffences in their starting or ending text spans are definitely unrelated.
const sourceStart = source.texts[0];
const targetStart = target.texts[0];
const sourceEnd = source.texts[source.texts.length - 1];
const targetEnd = target.texts[target.texts.length - 1];
const startLen = Math.min(sourceStart.length, targetStart.length);
const endLen = Math.min(sourceEnd.length, targetEnd.length);
return sourceStart.slice(0, startLen) !== targetStart.slice(0, startLen) ||
sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen);
}
/**
* Tests whether the provided string can be parsed as a number.
* @param s The string to test.
* @param roundTripOnly Indicates the resulting number matches the input when converted back to a string.
*/
function isValidNumberString(s: string, roundTripOnly: boolean): boolean {
if (s === "") return false;
const n = +s;
return isFinite(n) && (!roundTripOnly || "" + n === s);
}
/**
* @param text a valid bigint string excluding a trailing `n`, but including a possible prefix `-`. Use `isValidBigIntString(text, roundTripOnly)` before calling this function.
*/
function parseBigIntLiteralType(text: string) {
return getBigIntLiteralType(parseValidBigInt(text));
}
function isMemberOfStringMapping(source: Type, target: Type): boolean {
if (target.flags & TypeFlags.Any) {
return true;
}
if (target.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) {
return isTypeAssignableTo(source, target);
}
if (target.flags & TypeFlags.StringMapping) {
// We need to see whether applying the same mappings of the target
// onto the source would produce an identical type *and* that
// it's compatible with the inner-most non-string-mapped type.
//
// The intuition here is that if same mappings don't affect the source at all,
// and the source is compatible with the unmapped target, then they must
// still reside in the same domain.
const mappingStack = [];
while (target.flags & TypeFlags.StringMapping) {
mappingStack.unshift(target.symbol);
target = (target as StringMappingType).type;
}
const mappedSource = reduceLeft(mappingStack, (memo, value) => getStringMappingType(value, memo), source);
return mappedSource === source && isMemberOfStringMapping(source, target);
}
return false;
}
function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean {
if (target.flags & TypeFlags.Intersection) {
return every((target as IntersectionType).types, t => t === emptyTypeLiteralType || isValidTypeForTemplateLiteralPlaceholder(source, t));
}
if (target.flags & TypeFlags.String || isTypeAssignableTo(source, target)) {
return true;
}
if (source.flags & TypeFlags.StringLiteral) {
const value = (source as StringLiteralType).value;
return !!(target.flags & TypeFlags.Number && isValidNumberString(value, /*roundTripOnly*/ false) ||
target.flags & TypeFlags.BigInt && isValidBigIntString(value, /*roundTripOnly*/ false) ||
target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName ||
target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(source, target) ||
target.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType));
}
if (source.flags & TypeFlags.TemplateLiteral) {
const texts = (source as TemplateLiteralType).texts;
return texts.length === 2 && texts[0] === "" && texts[1] === "" && isTypeAssignableTo((source as TemplateLiteralType).types[0], target);
}
return false;
}
function inferTypesFromTemplateLiteralType(source: Type, target: TemplateLiteralType): Type[] | undefined {
return source.flags & TypeFlags.StringLiteral ? inferFromLiteralPartsToTemplateLiteral([(source as StringLiteralType).value], emptyArray, target) :
source.flags & TypeFlags.TemplateLiteral ?
arrayIsEqualTo((source as TemplateLiteralType).texts, target.texts) ? map((source as TemplateLiteralType).types, (s, i) => {
return isTypeAssignableTo(getBaseConstraintOrType(s), getBaseConstraintOrType(target.types[i])) ? s : getStringLikeTypeForType(s);
}) :
inferFromLiteralPartsToTemplateLiteral((source as TemplateLiteralType).texts, (source as TemplateLiteralType).types, target) :
undefined;
}
function isTypeMatchedByTemplateLiteralType(source: Type, target: TemplateLiteralType): boolean {
const inferences = inferTypesFromTemplateLiteralType(source, target);
return !!inferences && every(inferences, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, target.types[i]));
}
function getStringLikeTypeForType(type: Type) {
return type.flags & (TypeFlags.Any | TypeFlags.StringLike) ? type : getTemplateLiteralType(["", ""], [type]);
}
// This function infers from the text parts and type parts of a source literal to a target template literal. The number
// of text parts is always one more than the number of type parts, and a source string literal is treated as a source
// with one text part and zero type parts. The function returns an array of inferred string or template literal types
// corresponding to the placeholders in the target template literal, or undefined if the source doesn't match the target.
//
// We first check that the starting source text part matches the starting target text part, and that the ending source
// text part ends matches the ending target text part. We then iterate through the remaining target text parts, finding
// a match for each in the source and inferring string or template literal types created from the segments of the source
// that occur between the matches. During this iteration, seg holds the index of the current text part in the sourceTexts
// array and pos holds the current character position in the current text part.
//
// Consider inference from type `<<${string}>.<${number}-${number}>>` to type `<${string}.${string}>`, i.e.
// sourceTexts = ['<<', '>.<', '-', '>>']
// sourceTypes = [string, number, number]
// target.texts = ['<', '.', '>']
// We first match '<' in the target to the start of '<<' in the source and '>' in the target to the end of '>>' in
// the source. The first match for the '.' in target occurs at character 1 in the source text part at index 1, and thus
// the first inference is the template literal type `<${string}>`. The remainder of the source makes up the second
// inference, the template literal type `<${number}-${number}>`.
function inferFromLiteralPartsToTemplateLiteral(sourceTexts: readonly string[], sourceTypes: readonly Type[], target: TemplateLiteralType): Type[] | undefined {
const lastSourceIndex = sourceTexts.length - 1;
const sourceStartText = sourceTexts[0];
const sourceEndText = sourceTexts[lastSourceIndex];
const targetTexts = target.texts;
const lastTargetIndex = targetTexts.length - 1;
const targetStartText = targetTexts[0];
const targetEndText = targetTexts[lastTargetIndex];
if (
lastSourceIndex === 0 && sourceStartText.length < targetStartText.length + targetEndText.length ||
!sourceStartText.startsWith(targetStartText) || !sourceEndText.endsWith(targetEndText)
) return undefined;
const remainingEndText = sourceEndText.slice(0, sourceEndText.length - targetEndText.length);
const matches: Type[] = [];
let seg = 0;
let pos = targetStartText.length;
for (let i = 1; i < lastTargetIndex; i++) {
const delim = targetTexts[i];
if (delim.length > 0) {
let s = seg;
let p = pos;
while (true) {
p = getSourceText(s).indexOf(delim, p);
if (p >= 0) break;
s++;
if (s === sourceTexts.length) return undefined;
p = 0;
}
addMatch(s, p);
pos += delim.length;
}
else if (pos < getSourceText(seg).length) {
addMatch(seg, pos + 1);
}
else if (seg < lastSourceIndex) {
addMatch(seg + 1, 0);
}
else {
return undefined;
}
}
addMatch(lastSourceIndex, getSourceText(lastSourceIndex).length);
return matches;
function getSourceText(index: number) {
return index < lastSourceIndex ? sourceTexts[index] : remainingEndText;
}
function addMatch(s: number, p: number) {
const matchType = s === seg ?
getStringLiteralType(getSourceText(s).slice(pos, p)) :
getTemplateLiteralType(
[sourceTexts[seg].slice(pos), ...sourceTexts.slice(seg + 1, s), getSourceText(s).slice(0, p)],
sourceTypes.slice(seg, s),
);
matches.push(matchType);
seg = s;
pos = p;
}
}
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority = InferencePriority.None, contravariant = false) {
let bivariant = false;
let propagationType: Type;
let inferencePriority: number = InferencePriority.MaxValue;
let visited: Map;
let sourceStack: Type[];
let targetStack: Type[];
let expandingFlags = ExpandingFlags.None;
inferFromTypes(originalSource, originalTarget);
function inferFromTypes(source: Type, target: Type): void {
if (!couldContainTypeVariables(target) || isNoInferType(target)) {
return;
}
if (source === wildcardType || source === blockedStringType) {
// We are inferring from an 'any' type. We want to infer this type for every type parameter
// referenced in the target type, so we record it as the propagation type and infer from the
// target to itself. Then, as we find candidates we substitute the propagation type.
const savePropagationType = propagationType;
propagationType = source;
inferFromTypes(target, target);
propagationType = savePropagationType;
return;
}
if (source.aliasSymbol && source.aliasSymbol === target.aliasSymbol) {
if (source.aliasTypeArguments) {
// Source and target are types originating in the same generic type alias declaration.
// Simply infer from source type arguments to target type arguments, with defaults applied.
const params = getSymbolLinks(source.aliasSymbol).typeParameters!;
const minParams = getMinTypeArgumentCount(params);
const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration));
const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration));
inferFromTypeArguments(sourceTypes, targetTypes!, getAliasVariances(source.aliasSymbol));
}
// And if there weren't any type arguments, there's no reason to run inference as the types must be the same.
return;
}
if (source === target && source.flags & TypeFlags.UnionOrIntersection) {
// When source and target are the same union or intersection type, just relate each constituent
// type to itself.
for (const t of (source as UnionOrIntersectionType).types) {
inferFromTypes(t, t);
}
return;
}
if (target.flags & TypeFlags.Union) {
// First, infer between identically matching source and target constituents and remove the
// matching types.
const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (source as UnionType).types : [source], (target as UnionType).types, isTypeOrBaseIdenticalTo);
// Next, infer between closely matching source and target constituents and remove
// the matching types. Types closely match when they are instantiations of the same
// object type or instantiations of the same type alias.
const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy);
if (targets.length === 0) {
return;
}
target = getUnionType(targets);
if (sources.length === 0) {
// All source constituents have been matched and there is nothing further to infer from.
// However, simply making no inferences is undesirable because it could ultimately mean
// inferring a type parameter constraint. Instead, make a lower priority inference from
// the full source to whatever remains in the target. For example, when inferring from
// string to 'string | T', make a lower priority inference of string for T.
inferWithPriority(source, target, InferencePriority.NakedTypeVariable);
return;
}
source = getUnionType(sources);
}
else if (target.flags & TypeFlags.Intersection && !every((target as IntersectionType).types, isNonGenericObjectType)) {
// We reduce intersection types unless they're simple combinations of object types. For example,
// when inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and
// infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the
// string[] on the source side and infer string for T.
if (!(source.flags & TypeFlags.Union)) {
// Infer between identically matching source and target constituents and remove the matching types.
const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types : [source], (target as IntersectionType).types, isTypeIdenticalTo);
if (sources.length === 0 || targets.length === 0) {
return;
}
source = getIntersectionType(sources);
target = getIntersectionType(targets);
}
}
if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
if (isNoInferType(target)) {
return;
}
target = getActualTypeVariable(target);
}
if (target.flags & TypeFlags.TypeVariable) {
// Skip inference if the source is "blocked", which is used by the language service to
// prevent inference on nodes currently being edited.
if (isFromInferenceBlockedSource(source)) {
return;
}
const inference = getInferenceInfoForType(target);
if (inference) {
// If target is a type parameter, make an inference, unless the source type contains
// a "non-inferrable" type. Types with this flag set are markers used to prevent inference.
//
// For example:
// - anyFunctionType is a wildcard type that's used to avoid contextually typing functions;
// it's internal, so should not be exposed to the user by adding it as a candidate.
// - autoType (and autoArrayType) is a special "any" used in control flow; like anyFunctionType,
// it's internal and should not be observable.
// - silentNeverType is returned by getInferredType when instantiating a generic function for
// inference (and a type variable has no mapping).
//
// This flag is infectious; if we produce Box (where never is silentNeverType), Box is
// also non-inferrable.
//
// As a special case, also ignore nonInferrableAnyType, which is a special form of the any type
// used as a stand-in for binding elements when they are being inferred.
if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType) {
return;
}
if (!inference.isFixed) {
const candidate = propagationType || source;
if (candidate === blockedStringType) {
return;
}
if (inference.priority === undefined || priority < inference.priority) {
inference.candidates = undefined;
inference.contraCandidates = undefined;
inference.topLevel = true;
inference.priority = priority;
}
if (priority === inference.priority) {
// We make contravariant inferences only if we are in a pure contravariant position,
// i.e. only if we have not descended into a bivariant position.
if (contravariant && !bivariant) {
if (!contains(inference.contraCandidates, candidate)) {
inference.contraCandidates = append(inference.contraCandidates, candidate);
clearCachedInferences(inferences);
}
}
else if (!contains(inference.candidates, candidate)) {
inference.candidates = append(inference.candidates, candidate);
clearCachedInferences(inferences);
}
}
if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, target as TypeParameter)) {
inference.topLevel = false;
clearCachedInferences(inferences);
}
}
inferencePriority = Math.min(inferencePriority, priority);
return;
}
// 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, /*writing*/ false);
if (simplified !== target) {
inferFromTypes(source, simplified);
}
else if (target.flags & TypeFlags.IndexedAccess) {
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, /*writing*/ false), indexType, /*writing*/ false);
if (simplified && simplified !== target) {
inferFromTypes(source, simplified);
}
}
}
}
if (
getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
(source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target)
) &&
!((source as TypeReference).node && (target as TypeReference).node)
) {
// If source and target are references to the same generic type, infer from type arguments
inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target));
}
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
inferFromContravariantTypes((source as IndexType).type, (target as IndexType).type);
}
else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) {
const empty = createEmptyObjectTypeFromStringLiteral(source);
inferFromContravariantTypesWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof);
}
else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
inferFromTypes((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType);
inferFromTypes((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType);
}
else if (source.flags & TypeFlags.StringMapping && target.flags & TypeFlags.StringMapping) {
if ((source as StringMappingType).symbol === (target as StringMappingType).symbol) {
inferFromTypes((source as StringMappingType).type, (target as StringMappingType).type);
}
}
else if (source.flags & TypeFlags.Substitution) {
inferFromTypes((source as SubstitutionType).baseType, target);
inferWithPriority(getSubstitutionIntersection(source as SubstitutionType), target, InferencePriority.SubstituteSource); // Make substitute inference at a lower priority
}
else if (target.flags & TypeFlags.Conditional) {
invokeOnce(source, target as ConditionalType, inferToConditionalType);
}
else if (target.flags & TypeFlags.UnionOrIntersection) {
inferToMultipleTypes(source, (target as UnionOrIntersectionType).types, target.flags);
}
else if (source.flags & TypeFlags.Union) {
// Source is a union or intersection type, infer from each constituent type
const sourceTypes = (source as UnionOrIntersectionType).types;
for (const sourceType of sourceTypes) {
inferFromTypes(sourceType, target);
}
}
else if (target.flags & TypeFlags.TemplateLiteral) {
inferToTemplateLiteralType(source, target as TemplateLiteralType);
}
else {
source = getReducedType(source);
if (isGenericMappedType(source) && isGenericMappedType(target)) {
invokeOnce(source, target, inferFromGenericMappedTypes);
}
if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) {
const apparentSource = getApparentType(source);
// getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type.
// If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes`
// with the simplified source.
if (apparentSource !== source && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) {
return inferFromTypes(apparentSource, target);
}
source = apparentSource;
}
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
invokeOnce(source, target, inferFromObjectTypes);
}
}
}
function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) {
const savePriority = priority;
priority |= newPriority;
inferFromTypes(source, target);
priority = savePriority;
}
function inferFromContravariantTypesWithPriority(source: Type, target: Type, newPriority: InferencePriority) {
const savePriority = priority;
priority |= newPriority;
inferFromContravariantTypes(source, target);
priority = savePriority;
}
function inferToMultipleTypesWithPriority(source: Type, targets: Type[], targetFlags: TypeFlags, newPriority: InferencePriority) {
const savePriority = priority;
priority |= newPriority;
inferToMultipleTypes(source, targets, targetFlags);
priority = savePriority;
}
// Ensure an inference action is performed only once for the given source and target types.
// This includes two things:
// Avoiding inferring between the same pair of source and target types,
// and avoiding circularly inferring between source and target types.
// For an example of the last, consider if we are inferring between source type
// `type Deep = { next: Deep> }` and target type `type Loop = { next: Loop }`.
// We would then infer between the types of the `next` property: `Deep>` = `{ next: Deep>> }` and `Loop` = `{ next: Loop }`.
// We will then infer again between the types of the `next` property:
// `Deep>>` and `Loop`, and so on, such that we would be forever inferring
// between instantiations of the same types `Deep` and `Loop`.
// In particular, we would be inferring from increasingly deep instantiations of `Deep` to `Loop`,
// such that we would go on inferring forever, even though we would never infer
// between the same pair of types.
function invokeOnce(source: Source, target: Target, action: (source: Source, target: Target) => void) {
const key = source.id + "," + target.id;
const status = visited && visited.get(key);
if (status !== undefined) {
inferencePriority = Math.min(inferencePriority, status);
return;
}
(visited || (visited = new Map())).set(key, InferencePriority.Circularity);
const saveInferencePriority = inferencePriority;
inferencePriority = InferencePriority.MaxValue;
// We stop inferring and report a circularity if we encounter duplicate recursion identities on both
// the source side and the target side.
const saveExpandingFlags = expandingFlags;
(sourceStack ??= []).push(source);
(targetStack ??= []).push(target);
if (isDeeplyNestedType(source, sourceStack, sourceStack.length, 2)) expandingFlags |= ExpandingFlags.Source;
if (isDeeplyNestedType(target, targetStack, targetStack.length, 2)) expandingFlags |= ExpandingFlags.Target;
if (expandingFlags !== ExpandingFlags.Both) {
action(source, target);
}
else {
inferencePriority = InferencePriority.Circularity;
}
targetStack.pop();
sourceStack.pop();
expandingFlags = saveExpandingFlags;
visited.set(key, inferencePriority);
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
}
function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] {
let matchedSources: Type[] | undefined;
let matchedTargets: Type[] | undefined;
for (const t of targets) {
for (const s of sources) {
if (matches(s, t)) {
inferFromTypes(s, t);
matchedSources = appendIfUnique(matchedSources, s);
matchedTargets = appendIfUnique(matchedTargets, t);
}
}
}
return [
matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources,
matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets,
];
}
function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) {
const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length;
for (let i = 0; i < count; i++) {
if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) {
inferFromContravariantTypes(sourceTypes[i], targetTypes[i]);
}
else {
inferFromTypes(sourceTypes[i], targetTypes[i]);
}
}
}
function inferFromContravariantTypes(source: Type, target: Type) {
contravariant = !contravariant;
inferFromTypes(source, target);
contravariant = !contravariant;
}
function inferFromContravariantTypesIfStrictFunctionTypes(source: Type, target: Type) {
if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) {
inferFromContravariantTypes(source, target);
}
else {
inferFromTypes(source, target);
}
}
function getInferenceInfoForType(type: Type) {
if (type.flags & TypeFlags.TypeVariable) {
for (const inference of inferences) {
if (type === inference.typeParameter) {
return inference;
}
}
}
return undefined;
}
function getSingleTypeVariableFromIntersectionTypes(types: Type[]) {
let typeVariable: Type | undefined;
for (const type of types) {
const t = type.flags & TypeFlags.Intersection && find((type as IntersectionType).types, t => !!getInferenceInfoForType(t));
if (!t || typeVariable && t !== typeVariable) {
return undefined;
}
typeVariable = t;
}
return typeVariable;
}
function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) {
let typeVariableCount = 0;
if (targetFlags & TypeFlags.Union) {
let nakedTypeVariable: Type | undefined;
const sources = source.flags & TypeFlags.Union ? (source as UnionType).types : [source];
const matched = new Array(sources.length);
let inferenceCircularity = false;
// First infer to types that are not naked type variables. For each source type we
// track whether inferences were made from that particular type to some target with
// equal priority (i.e. of equal quality) to what we would infer for a naked type
// parameter.
for (const t of targets) {
if (getInferenceInfoForType(t)) {
nakedTypeVariable = t;
typeVariableCount++;
}
else {
for (let i = 0; i < sources.length; i++) {
const saveInferencePriority = inferencePriority;
inferencePriority = InferencePriority.MaxValue;
inferFromTypes(sources[i], t);
if (inferencePriority === priority) matched[i] = true;
inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity;
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
}
}
}
if (typeVariableCount === 0) {
// If every target is an intersection of types containing a single naked type variable,
// make a lower priority inference to that type variable. This handles inferring from
// 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T.
const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets);
if (intersectionTypeVariable) {
inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable);
}
return;
}
// If the target has a single naked type variable and no inference circularities were
// encountered above (meaning we explored the types fully), create a union of the source
// types from which no inferences have been made so far and infer from that union to the
// naked type variable.
if (typeVariableCount === 1 && !inferenceCircularity) {
const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s);
if (unmatched.length) {
inferFromTypes(getUnionType(unmatched), nakedTypeVariable!);
return;
}
}
}
else {
// We infer from types that are not naked type variables first so that inferences we
// make from nested naked type variables and given slightly higher priority by virtue
// of being first in the candidates array.
for (const t of targets) {
if (getInferenceInfoForType(t)) {
typeVariableCount++;
}
else {
inferFromTypes(source, t);
}
}
}
// Inferences directly to naked type variables are given lower priority as they are
// less specific. For example, when inferring from Promise to T | Promise,
// we want to infer string for T, not Promise | string. For intersection types
// we only infer to single naked type variables.
if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) {
for (const t of targets) {
if (getInferenceInfoForType(t)) {
inferWithPriority(source, t, InferencePriority.NakedTypeVariable);
}
}
}
}
function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean {
if ((constraintType.flags & TypeFlags.Union) || (constraintType.flags & TypeFlags.Intersection)) {
let result = false;
for (const type of (constraintType as (UnionType | IntersectionType)).types) {
result = inferToMappedType(source, target, type) || result;
}
return result;
}
if (constraintType.flags & TypeFlags.Index) {
// We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X },
// where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source
// type and then make a secondary inference from that type to T. We make a secondary inference
// such that direct inferences to T get priority over inferences to Partial, for example.
const inference = getInferenceInfoForType((constraintType as IndexType).type);
if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) {
const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType as IndexType);
if (inferredType) {
// We assign a lower priority to inferences made from types containing non-inferrable
// types because we may only have a partial result (i.e. we may have failed to make
// reverse inferences for some properties).
inferWithPriority(
inferredType,
inference.typeParameter,
getObjectFlags(source) & ObjectFlags.NonInferrableType ?
InferencePriority.PartialHomomorphicMappedType :
InferencePriority.HomomorphicMappedType,
);
}
}
return true;
}
if (constraintType.flags & TypeFlags.TypeParameter) {
// We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type
// parameter. First infer from 'keyof S' to K.
inferWithPriority(getIndexType(source, /*indexFlags*/ !!source.pattern ? IndexFlags.NoIndexSignatures : IndexFlags.None), constraintType, InferencePriority.MappedTypeConstraint);
// If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X },
// where K extends keyof T, we make the same inferences as for a homomorphic mapped type
// { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a
// Pick.
const extendedConstraint = getConstraintOfType(constraintType);
if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) {
return true;
}
// If no inferences can be made to K's constraint, infer from a union of the property types
// in the source to the template type X.
const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol);
const indexTypes = map(getIndexInfosOfType(source), info => info !== enumNumberIndexInfo ? info.type : neverType);
inferFromTypes(getUnionType(concatenate(propTypes, indexTypes)), getTemplateTypeFromMappedType(target));
return true;
}
return false;
}
function inferToConditionalType(source: Type, target: ConditionalType) {
if (source.flags & TypeFlags.Conditional) {
inferFromTypes((source as ConditionalType).checkType, target.checkType);
inferFromTypes((source as ConditionalType).extendsType, target.extendsType);
inferFromTypes(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target));
inferFromTypes(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target));
}
else {
const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)];
inferToMultipleTypesWithPriority(source, targetTypes, target.flags, contravariant ? InferencePriority.ContravariantConditional : 0);
}
}
function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) {
const matches = inferTypesFromTemplateLiteralType(source, target);
const types = target.types;
// When the target template literal contains only placeholders (meaning that inference is intended to extract
// single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for
// each placeholder such that instantiation with the inferred value(s) produces 'never', a type for which an
// assignment check will fail. If we make no inferences, we'll likely end up with the constraint 'string' which,
// upon instantiation, would collapse all the placeholders to just 'string', and an assignment check might
// succeed. That would be a pointless and confusing outcome.
if (matches || every(target.texts, s => s.length === 0)) {
for (let i = 0; i < types.length; i++) {
const source = matches ? matches[i] : neverType;
const target = types[i];
// If we are inferring from a string literal type to a type variable whose constraint includes one of the
// allowed template literal placeholder types, infer from a literal type corresponding to the constraint.
if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.TypeVariable) {
const inferenceContext = getInferenceInfoForType(target);
const constraint = inferenceContext ? getBaseConstraintOfType(inferenceContext.typeParameter) : undefined;
if (constraint && !isTypeAny(constraint)) {
const constraintTypes = constraint.flags & TypeFlags.Union ? (constraint as UnionType).types : [constraint];
let allTypeFlags: TypeFlags = reduceLeft(constraintTypes, (flags, t) => flags | t.flags, 0 as TypeFlags);
// If the constraint contains `string`, we don't need to look for a more preferred type
if (!(allTypeFlags & TypeFlags.String)) {
const str = (source as StringLiteralType).value;
// If the type contains `number` or a number literal and the string isn't a valid number, exclude numbers
if (allTypeFlags & TypeFlags.NumberLike && !isValidNumberString(str, /*roundTripOnly*/ true)) {
allTypeFlags &= ~TypeFlags.NumberLike;
}
// If the type contains `bigint` or a bigint literal and the string isn't a valid bigint, exclude bigints
if (allTypeFlags & TypeFlags.BigIntLike && !isValidBigIntString(str, /*roundTripOnly*/ true)) {
allTypeFlags &= ~TypeFlags.BigIntLike;
}
// for each type in the constraint, find the highest priority matching type
const matchingType = reduceLeft(constraintTypes, (left, right) =>
!(right.flags & allTypeFlags) ? left :
left.flags & TypeFlags.String ? left : right.flags & TypeFlags.String ? source :
left.flags & TypeFlags.TemplateLiteral ? left : right.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, right as TemplateLiteralType) ? source :
left.flags & TypeFlags.StringMapping ? left : right.flags & TypeFlags.StringMapping && str === applyStringMapping(right.symbol, str) ? source :
left.flags & TypeFlags.StringLiteral ? left : right.flags & TypeFlags.StringLiteral && (right as StringLiteralType).value === str ? right :
left.flags & TypeFlags.Number ? left : right.flags & TypeFlags.Number ? getNumberLiteralType(+str) :
left.flags & TypeFlags.Enum ? left : right.flags & TypeFlags.Enum ? getNumberLiteralType(+str) :
left.flags & TypeFlags.NumberLiteral ? left : right.flags & TypeFlags.NumberLiteral && (right as NumberLiteralType).value === +str ? right :
left.flags & TypeFlags.BigInt ? left : right.flags & TypeFlags.BigInt ? parseBigIntLiteralType(str) :
left.flags & TypeFlags.BigIntLiteral ? left : right.flags & TypeFlags.BigIntLiteral && pseudoBigIntToString((right as BigIntLiteralType).value) === str ? right :
left.flags & TypeFlags.Boolean ? left : right.flags & TypeFlags.Boolean ? str === "true" ? trueType : str === "false" ? falseType : booleanType :
left.flags & TypeFlags.BooleanLiteral ? left : right.flags & TypeFlags.BooleanLiteral && (right as IntrinsicType).intrinsicName === str ? right :
left.flags & TypeFlags.Undefined ? left : right.flags & TypeFlags.Undefined && (right as IntrinsicType).intrinsicName === str ? right :
left.flags & TypeFlags.Null ? left : right.flags & TypeFlags.Null && (right as IntrinsicType).intrinsicName === str ? right :
left, neverType as Type);
if (!(matchingType.flags & TypeFlags.Never)) {
inferFromTypes(matchingType, target);
continue;
}
}
}
}
inferFromTypes(source, target);
}
}
}
function inferFromGenericMappedTypes(source: MappedType, target: MappedType) {
// The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer
// from S to T and from X to Y.
inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target));
inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target));
const sourceNameType = getNameTypeFromMappedType(source);
const targetNameType = getNameTypeFromMappedType(target);
if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType);
}
function inferFromObjectTypes(source: Type, target: Type) {
if (
getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
(source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target)
)
) {
// If source and target are references to the same generic type, infer from type arguments
inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target));
return;
}
if (isGenericMappedType(source) && isGenericMappedType(target)) {
inferFromGenericMappedTypes(source, target);
}
if (getObjectFlags(target) & ObjectFlags.Mapped && !(target as MappedType).declaration.nameType) {
const constraintType = getConstraintTypeFromMappedType(target as MappedType);
if (inferToMappedType(source, target as MappedType, constraintType)) {
return;
}
}
// Infer from the members of source and target only if the two types are possibly related
if (!typesDefinitelyUnrelated(source, target)) {
if (isArrayOrTupleType(source)) {
if (isTupleType(target)) {
const sourceArity = getTypeReferenceArity(source);
const targetArity = getTypeReferenceArity(target);
const elementTypes = getTypeArguments(target);
const elementFlags = target.target.elementFlags;
// When source and target are tuple types with the same structure (fixed, variadic, and rest are matched
// to the same kind in each position), simply infer between the element types.
if (isTupleType(source) && isTupleTypeStructureMatching(source, target)) {
for (let i = 0; i < targetArity; i++) {
inferFromTypes(getTypeArguments(source)[i], elementTypes[i]);
}
return;
}
const startLength = isTupleType(source) ? Math.min(source.target.fixedLength, target.target.fixedLength) : 0;
const endLength = Math.min(isTupleType(source) ? getEndElementCount(source.target, ElementFlags.Fixed) : 0, target.target.combinedFlags & ElementFlags.Variable ? getEndElementCount(target.target, ElementFlags.Fixed) : 0);
// Infer between starting fixed elements.
for (let i = 0; i < startLength; i++) {
inferFromTypes(getTypeArguments(source)[i], elementTypes[i]);
}
if (!isTupleType(source) || sourceArity - startLength - endLength === 1 && source.target.elementFlags[startLength] & ElementFlags.Rest) {
// Single rest element remains in source, infer from that to every element in target
const restType = getTypeArguments(source)[startLength];
for (let i = startLength; i < targetArity - endLength; i++) {
inferFromTypes(elementFlags[i] & ElementFlags.Variadic ? createArrayType(restType) : restType, elementTypes[i]);
}
}
else {
const middleLength = targetArity - startLength - endLength;
if (middleLength === 2) {
if (elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic) {
// Middle of target is [...T, ...U] and source is tuple type
const targetInfo = getInferenceInfoForType(elementTypes[startLength]);
if (targetInfo && targetInfo.impliedArity !== undefined) {
// Infer slices from source based on implied arity of T.
inferFromTypes(sliceTupleType(source, startLength, endLength + sourceArity - targetInfo.impliedArity), elementTypes[startLength]);
inferFromTypes(sliceTupleType(source, startLength + targetInfo.impliedArity, endLength), elementTypes[startLength + 1]);
}
}
else if (elementFlags[startLength] & ElementFlags.Variadic && elementFlags[startLength + 1] & ElementFlags.Rest) {
// Middle of target is [...T, ...rest] and source is tuple type
// if T is constrained by a fixed-size tuple we might be able to use its arity to infer T
const param = getInferenceInfoForType(elementTypes[startLength])?.typeParameter;
const constraint = param && getBaseConstraintOfType(param);
if (constraint && isTupleType(constraint) && !(constraint.target.combinedFlags & ElementFlags.Variable)) {
const impliedArity = constraint.target.fixedLength;
inferFromTypes(sliceTupleType(source, startLength, sourceArity - (startLength + impliedArity)), elementTypes[startLength]);
inferFromTypes(getElementTypeOfSliceOfTupleType(source, startLength + impliedArity, endLength)!, elementTypes[startLength + 1]);
}
}
else if (elementFlags[startLength] & ElementFlags.Rest && elementFlags[startLength + 1] & ElementFlags.Variadic) {
// Middle of target is [...rest, ...T] and source is tuple type
// if T is constrained by a fixed-size tuple we might be able to use its arity to infer T
const param = getInferenceInfoForType(elementTypes[startLength + 1])?.typeParameter;
const constraint = param && getBaseConstraintOfType(param);
if (constraint && isTupleType(constraint) && !(constraint.target.combinedFlags & ElementFlags.Variable)) {
const impliedArity = constraint.target.fixedLength;
const endIndex = sourceArity - getEndElementCount(target.target, ElementFlags.Fixed);
const startIndex = endIndex - impliedArity;
const trailingSlice = createTupleType(getTypeArguments(source).slice(startIndex, endIndex), source.target.elementFlags.slice(startIndex, endIndex), /*readonly*/ false, source.target.labeledElementDeclarations && source.target.labeledElementDeclarations.slice(startIndex, endIndex));
inferFromTypes(getElementTypeOfSliceOfTupleType(source, startLength, endLength + impliedArity)!, elementTypes[startLength]);
inferFromTypes(trailingSlice, elementTypes[startLength + 1]);
}
}
}
else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Variadic) {
// Middle of target is exactly one variadic element. Infer the slice between the fixed parts in the source.
// If target ends in optional element(s), make a lower priority a speculative inference.
const endsInOptional = target.target.elementFlags[targetArity - 1] & ElementFlags.Optional;
const sourceSlice = sliceTupleType(source, startLength, endLength);
inferWithPriority(sourceSlice, elementTypes[startLength], endsInOptional ? InferencePriority.SpeculativeTuple : 0);
}
else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Rest) {
// Middle of target is exactly one rest element. If middle of source is not empty, infer union of middle element types.
const restType = getElementTypeOfSliceOfTupleType(source, startLength, endLength);
if (restType) {
inferFromTypes(restType, elementTypes[startLength]);
}
}
}
// Infer between ending fixed elements
for (let i = 0; i < endLength; i++) {
inferFromTypes(getTypeArguments(source)[sourceArity - i - 1], elementTypes[targetArity - i - 1]);
}
return;
}
if (isArrayType(target)) {
inferFromIndexTypes(source, target);
return;
}
}
inferFromProperties(source, target);
inferFromSignatures(source, target, SignatureKind.Call);
inferFromSignatures(source, target, SignatureKind.Construct);
inferFromIndexTypes(source, target);
}
}
function inferFromProperties(source: Type, target: Type) {
const properties = getPropertiesOfObjectType(target);
for (const targetProp of properties) {
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
if (sourceProp && !some(sourceProp.declarations, hasSkipDirectInferenceFlag)) {
inferFromTypes(
removeMissingType(getTypeOfSymbol(sourceProp), !!(sourceProp.flags & SymbolFlags.Optional)),
removeMissingType(getTypeOfSymbol(targetProp), !!(targetProp.flags & SymbolFlags.Optional)),
);
}
}
}
function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) {
const sourceSignatures = getSignaturesOfType(source, kind);
const sourceLen = sourceSignatures.length;
if (sourceLen > 0) {
// We match source and target signatures from the bottom up, and if the source has fewer signatures
// than the target, we infer from the first source signature to the excess target signatures.
const targetSignatures = getSignaturesOfType(target, kind);
const targetLen = targetSignatures.length;
for (let i = 0; i < targetLen; i++) {
const sourceIndex = Math.max(sourceLen - targetLen + i, 0);
inferFromSignature(getBaseSignature(sourceSignatures[sourceIndex]), getErasedSignature(targetSignatures[i]));
}
}
}
function inferFromSignature(source: Signature, target: Signature) {
if (!(source.flags & SignatureFlags.IsNonInferrable)) {
const saveBivariant = bivariant;
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
// Once we descend into a bivariant signature we remain bivariant for all nested inferences
bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor;
applyToParameterTypes(source, target, inferFromContravariantTypesIfStrictFunctionTypes);
bivariant = saveBivariant;
}
applyToReturnTypes(source, target, inferFromTypes);
}
function inferFromIndexTypes(source: Type, target: Type) {
// Inferences across mapped type index signatures are pretty much the same a inferences to homomorphic variables
const priority = (getObjectFlags(source) & getObjectFlags(target) & ObjectFlags.Mapped) ? InferencePriority.HomomorphicMappedType : 0;
const indexInfos = getIndexInfosOfType(target);
if (isObjectTypeWithInferableIndex(source)) {
for (const targetInfo of indexInfos) {
const propTypes: Type[] = [];
for (const prop of getPropertiesOfType(source)) {
if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), targetInfo.keyType)) {
const propType = getTypeOfSymbol(prop);
propTypes.push(prop.flags & SymbolFlags.Optional ? removeMissingOrUndefinedType(propType) : propType);
}
}
for (const info of getIndexInfosOfType(source)) {
if (isApplicableIndexType(info.keyType, targetInfo.keyType)) {
propTypes.push(info.type);
}
}
if (propTypes.length) {
inferWithPriority(getUnionType(propTypes), targetInfo.type, priority);
}
}
}
for (const targetInfo of indexInfos) {
const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType);
if (sourceInfo) {
inferWithPriority(sourceInfo.type, targetInfo.type, priority);
}
}
}
}
function isTypeOrBaseIdenticalTo(s: Type, t: Type) {
return t === missingType ? s === t :
(isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral));
}
function isTypeCloselyMatchedBy(s: Type, t: Type) {
return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol ||
s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol);
}
function hasPrimitiveConstraint(type: TypeParameter): boolean {
const constraint = getConstraintOfTypeParameter(type);
return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping);
}
function isObjectLiteralType(type: Type) {
return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral);
}
function isObjectOrArrayLiteralType(type: Type) {
return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral));
}
function unionObjectAndArrayLiteralCandidates(candidates: Type[]): Type[] {
if (candidates.length > 1) {
const objectLiterals = filter(candidates, isObjectOrArrayLiteralType);
if (objectLiterals.length) {
const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype);
return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]);
}
}
return candidates;
}
function getContravariantInference(inference: InferenceInfo) {
return inference.priority! & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!);
}
function getCovariantInference(inference: InferenceInfo, signature: Signature) {
// Extract all object and array literal types and replace them with a single widened and normalized type.
const candidates = unionObjectAndArrayLiteralCandidates(inference.candidates!);
// We widen inferred literal types if
// all inferences were made to top-level occurrences of the type parameter, and
// the type parameter has no constraint or its constraint includes no primitive or literal types, and
// the type parameter was fixed during inference or does not occur at top-level in the return type.
const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter) || isConstTypeVariable(inference.typeParameter);
const widenLiteralTypes = !primitiveConstraint && inference.topLevel &&
(inference.isFixed || !isTypeParameterAtTopLevelInReturnType(signature, inference.typeParameter));
const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) :
widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) :
candidates;
// If all inferences were made from a position that implies a combined result, infer a union type.
// Otherwise, infer a common supertype.
const unwidenedType = inference.priority! & InferencePriority.PriorityImpliesCombination ?
getUnionType(baseCandidates, UnionReduction.Subtype) :
getCommonSupertype(baseCandidates);
return getWidenedType(unwidenedType);
}
function getInferredType(context: InferenceContext, index: number): Type {
const inference = context.inferences[index];
if (!inference.inferredType) {
let inferredType: Type | undefined;
let fallbackType: Type | undefined;
if (context.signature) {
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, context.signature) : undefined;
const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined;
if (inferredCovariantType || inferredContravariantType) {
// If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never',
// all co-variant inferences are assignable to it (i.e. it isn't one of a conflicting set of candidates), it is
// assignable to some contra-variant inference, and no other type parameter is constrained to this type parameter
// and has inferences that would conflict. Otherwise, we prefer the contra-variant inference.
// Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it
// and it would spoil the overall inference.
const preferCovariantType = inferredCovariantType && (!inferredContravariantType ||
!(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any)) &&
some(inference.contraCandidates, t => isTypeAssignableTo(inferredCovariantType, t)) &&
every(context.inferences, other =>
other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter ||
every(other.candidates, t => isTypeAssignableTo(t, inferredCovariantType))));
inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType;
fallbackType = preferCovariantType ? inferredContravariantType : inferredCovariantType;
}
else if (context.flags & InferenceFlags.NoDefault) {
// We use silentNeverType as the wildcard that signals no inferences.
inferredType = silentNeverType;
}
else {
// Infer either the default or the empty object type when no inferences were
// made. It is important to remember that in this case, inference still
// succeeds, meaning there is no error for not having inference candidates. An
// inference error only occurs when there are *conflicting* candidates, i.e.
// candidates with no common supertype.
const defaultType = getDefaultFromTypeParameter(inference.typeParameter);
if (defaultType) {
// Instantiate the default type. Any forward reference to a type
// parameter should be instantiated to the empty object type.
inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper));
}
}
}
else {
inferredType = getTypeFromInference(inference);
}
inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault));
const constraint = getConstraintOfTypeParameter(inference.typeParameter);
if (constraint) {
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
}
}
clearActiveMapperCaches();
}
return inference.inferredType;
}
function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type {
return isInJavaScriptFile ? anyType : unknownType;
}
function getInferredTypes(context: InferenceContext): Type[] {
const result: Type[] = [];
for (let i = 0; i < context.inferences.length; i++) {
result.push(getInferredType(context, i));
}
return result;
}
// EXPRESSION TYPE CHECKING
function getCannotFindNameDiagnosticForName(node: Identifier): DiagnosticMessage {
switch (node.escapedText) {
case "document":
case "console":
return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom;
case "$":
return compilerOptions.types
? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig
: Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery;
case "describe":
case "suite":
case "it":
case "test":
return compilerOptions.types
? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig
: Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha;
case "process":
case "require":
case "Buffer":
case "module":
return compilerOptions.types
? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig
: Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode;
case "Bun":
return compilerOptions.types
? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_Bun_Try_npm_i_save_dev_types_Slashbun_and_then_add_bun_to_the_types_field_in_your_tsconfig
: Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_Bun_Try_npm_i_save_dev_types_Slashbun;
case "Map":
case "Set":
case "Promise":
case "Symbol":
case "WeakMap":
case "WeakSet":
case "Iterator":
case "AsyncIterator":
case "SharedArrayBuffer":
case "Atomics":
case "AsyncIterable":
case "AsyncIterableIterator":
case "AsyncGenerator":
case "AsyncGeneratorFunction":
case "BigInt":
case "Reflect":
case "BigInt64Array":
case "BigUint64Array":
return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_1_or_later;
case "await":
if (isCallExpression(node.parent)) {
return Diagnostics.Cannot_find_name_0_Did_you_mean_to_write_this_in_an_async_function;
}
// falls through
default:
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer;
}
else {
return Diagnostics.Cannot_find_name_0;
}
}
}
function getResolvedSymbol(node: Identifier): Symbol {
const links = getNodeLinks(node);
if (!links.resolvedSymbol) {
links.resolvedSymbol = !nodeIsMissing(node) &&
resolveName(
node,
node,
SymbolFlags.Value | SymbolFlags.ExportValue,
getCannotFindNameDiagnosticForName(node),
!isWriteOnlyAccess(node),
/*excludeGlobals*/ false,
) || unknownSymbol;
}
return links.resolvedSymbol;
}
function isInAmbientOrTypeNode(node: Node): boolean {
return !!(node.flags & NodeFlags.Ambient || findAncestor(node, n => isInterfaceDeclaration(n) || isTypeAliasDeclaration(n) || isTypeLiteralNode(n)));
}
// Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers
// separated by dots). The key consists of the id of the symbol referenced by the
// leftmost identifier followed by zero or more property names separated by dots.
// The result is undefined if the reference isn't a dotted name.
function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined {
switch (node.kind) {
case SyntaxKind.Identifier:
if (!isThisInTypeQuery(node)) {
const symbol = getResolvedSymbol(node as Identifier);
return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${getSymbolId(symbol)}` : undefined;
}
// falls through
case SyntaxKind.ThisKeyword:
return `0|${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}`;
case SyntaxKind.NonNullExpression:
case SyntaxKind.ParenthesizedExpression:
return getFlowCacheKey((node as NonNullExpression | ParenthesizedExpression).expression, declaredType, initialType, flowContainer);
case SyntaxKind.QualifiedName:
const left = getFlowCacheKey((node as QualifiedName).left, declaredType, initialType, flowContainer);
return left && `${left}.${(node as QualifiedName).right.escapedText}`;
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
const propName = getAccessedPropertyName(node as AccessExpression);
if (propName !== undefined) {
const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer);
return key && `${key}.${propName}`;
}
if (isElementAccessExpression(node) && isIdentifier(node.argumentExpression)) {
const symbol = getResolvedSymbol(node.argumentExpression);
if (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol)) {
const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer);
return key && `${key}.@${getSymbolId(symbol)}`;
}
}
break;
case SyntaxKind.ObjectBindingPattern:
case SyntaxKind.ArrayBindingPattern:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.MethodDeclaration:
// Handle pseudo-references originating in getNarrowedTypeOfSymbol.
return `${getNodeId(node)}#${getTypeId(declaredType)}`;
}
return undefined;
}
function isMatchingReference(source: Node, target: Node): boolean {
switch (target.kind) {
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.NonNullExpression:
return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression);
case SyntaxKind.BinaryExpression:
return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) ||
(isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right));
}
switch (source.kind) {
case SyntaxKind.MetaProperty:
return target.kind === SyntaxKind.MetaProperty
&& (source as MetaProperty).keywordToken === (target as MetaProperty).keywordToken
&& (source as MetaProperty).name.escapedText === (target as MetaProperty).name.escapedText;
case SyntaxKind.Identifier:
case SyntaxKind.PrivateIdentifier:
return isThisInTypeQuery(source) ?
target.kind === SyntaxKind.ThisKeyword :
target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) ||
(isVariableDeclaration(target) || isBindingElement(target)) &&
getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfDeclaration(target);
case SyntaxKind.ThisKeyword:
return target.kind === SyntaxKind.ThisKeyword;
case SyntaxKind.SuperKeyword:
return target.kind === SyntaxKind.SuperKeyword;
case SyntaxKind.NonNullExpression:
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.SatisfiesExpression:
return isMatchingReference((source as NonNullExpression | ParenthesizedExpression | SatisfiesExpression).expression, target);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
const sourcePropertyName = getAccessedPropertyName(source as AccessExpression);
if (sourcePropertyName !== undefined) {
const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined;
if (targetPropertyName !== undefined) {
return targetPropertyName === sourcePropertyName && isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression);
}
}
if (isElementAccessExpression(source) && isElementAccessExpression(target) && isIdentifier(source.argumentExpression) && isIdentifier(target.argumentExpression)) {
const symbol = getResolvedSymbol(source.argumentExpression);
if (symbol === getResolvedSymbol(target.argumentExpression) && (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol))) {
return isMatchingReference(source.expression, target.expression);
}
}
break;
case SyntaxKind.QualifiedName:
return isAccessExpression(target) &&
(source as QualifiedName).right.escapedText === getAccessedPropertyName(target) &&
isMatchingReference((source as QualifiedName).left, target.expression);
case SyntaxKind.BinaryExpression:
return (isBinaryExpression(source) && source.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source.right, target));
}
return false;
}
function getAccessedPropertyName(access: AccessExpression | BindingElement | ParameterDeclaration): __String | undefined {
if (isPropertyAccessExpression(access)) {
return access.name.escapedText;
}
if (isElementAccessExpression(access)) {
return tryGetElementAccessExpressionName(access);
}
if (isBindingElement(access)) {
const name = getDestructuringPropertyName(access);
return name ? escapeLeadingUnderscores(name) : undefined;
}
if (isParameter(access)) {
return ("" + access.parent.parameters.indexOf(access)) as __String;
}
return undefined;
}
function tryGetNameFromType(type: Type) {
return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName :
type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined;
}
function tryGetElementAccessExpressionName(node: ElementAccessExpression) {
return isStringOrNumericLiteralLike(node.argumentExpression) ? escapeLeadingUnderscores(node.argumentExpression.text) :
isEntityNameExpression(node.argumentExpression) ? tryGetNameFromEntityNameExpression(node.argumentExpression) : undefined;
}
function tryGetNameFromEntityNameExpression(node: EntityNameOrEntityNameExpression) {
const symbol = resolveEntityName(node, SymbolFlags.Value, /*ignoreErrors*/ true);
if (!symbol || !(isConstantVariable(symbol) || (symbol.flags & SymbolFlags.EnumMember))) return undefined;
const declaration = symbol.valueDeclaration;
if (declaration === undefined) return undefined;
const type = tryGetTypeFromEffectiveTypeNode(declaration);
if (type) {
const name = tryGetNameFromType(type);
if (name !== undefined) {
return name;
}
}
if (hasOnlyExpressionInitializer(declaration) && isBlockScopedNameDeclaredBeforeUse(declaration, node)) {
const initializer = getEffectiveInitializer(declaration);
if (initializer) {
const initializerType = isBindingPattern(declaration.parent) ? getTypeForBindingElement(declaration as BindingElement) : getTypeOfExpression(initializer);
return initializerType && tryGetNameFromType(initializerType);
}
if (isEnumMember(declaration)) {
return getTextOfPropertyName(declaration.name);
}
}
return undefined;
}
function containsMatchingReference(source: Node, target: Node) {
while (isAccessExpression(source)) {
source = source.expression;
if (isMatchingReference(source, target)) {
return true;
}
}
return false;
}
function optionalChainContainsReference(source: Node, target: Node) {
while (isOptionalChain(source)) {
source = source.expression;
if (isMatchingReference(source, target)) {
return true;
}
}
return false;
}
function isDiscriminantProperty(type: Type | undefined, name: __String) {
if (type && type.flags & TypeFlags.Union) {
const prop = getUnionOrIntersectionProperty(type as UnionType, name);
if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) {
// NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty
if ((prop as TransientSymbol).links.isDiscriminantProperty === undefined) {
(prop as TransientSymbol).links.isDiscriminantProperty = ((prop as TransientSymbol).links.checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant &&
!isGenericType(getTypeOfSymbol(prop));
}
return !!(prop as TransientSymbol).links.isDiscriminantProperty;
}
}
return false;
}
function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined {
let result: Symbol[] | undefined;
for (const sourceProperty of sourceProperties) {
if (isDiscriminantProperty(target, sourceProperty.escapedName)) {
if (result) {
result.push(sourceProperty);
continue;
}
result = [sourceProperty];
}
}
return result;
}
// Given a set of constituent types and a property name, create and return a map keyed by the literal
// types of the property by that name in each constituent type. No map is returned if some key property
// has a non-literal type or if less than 10 or less than 50% of the constituents have a unique key.
// Entries with duplicate keys have unknownType as the value.
function mapTypesByKeyProperty(types: Type[], name: __String) {
const map = new Map();
let count = 0;
for (const type of types) {
if (type.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) {
const discriminant = getTypeOfPropertyOfType(type, name);
if (discriminant) {
if (!isLiteralType(discriminant)) {
return undefined;
}
let duplicate = false;
forEachType(discriminant, t => {
const id = getTypeId(getRegularTypeOfLiteralType(t));
const existing = map.get(id);
if (!existing) {
map.set(id, type);
}
else if (existing !== unknownType) {
map.set(id, unknownType);
duplicate = true;
}
});
if (!duplicate) count++;
}
}
}
return count >= 10 && count * 2 >= types.length ? map : undefined;
}
// Return the name of a discriminant property for which it was possible and feasible to construct a map of
// constituent types keyed by the literal types of the property by that name in each constituent type.
function getKeyPropertyName(unionType: UnionType): __String | undefined {
const types = unionType.types;
// We only construct maps for unions with many non-primitive constituents.
if (
types.length < 10 || getObjectFlags(unionType) & ObjectFlags.PrimitiveUnion ||
countWhere(types, t => !!(t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive))) < 10
) {
return undefined;
}
if (unionType.keyPropertyName === undefined) {
// The candidate key property name is the name of the first property with a unit type in one of the
// constituent types.
const keyPropertyName = forEach(types, t =>
t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) ?
forEach(getPropertiesOfType(t), p => isUnitType(getTypeOfSymbol(p)) ? p.escapedName : undefined) :
undefined);
const mapByKeyProperty = keyPropertyName && mapTypesByKeyProperty(types, keyPropertyName);
unionType.keyPropertyName = mapByKeyProperty ? keyPropertyName : "" as __String;
unionType.constituentMap = mapByKeyProperty;
}
return (unionType.keyPropertyName as string).length ? unionType.keyPropertyName : undefined;
}
// Given a union type for which getKeyPropertyName returned a non-undefined result, return the constituent
// that corresponds to the given key type for that property name.
function getConstituentTypeForKeyType(unionType: UnionType, keyType: Type) {
const result = unionType.constituentMap?.get(getTypeId(getRegularTypeOfLiteralType(keyType)));
return result !== unknownType ? result : undefined;
}
function getMatchingUnionConstituentForType(unionType: UnionType, type: Type) {
const keyPropertyName = getKeyPropertyName(unionType);
const propType = keyPropertyName && getTypeOfPropertyOfType(type, keyPropertyName);
return propType && getConstituentTypeForKeyType(unionType, propType);
}
function getMatchingUnionConstituentForObjectLiteral(unionType: UnionType, node: ObjectLiteralExpression) {
const keyPropertyName = getKeyPropertyName(unionType);
const propNode = keyPropertyName && find(node.properties, p =>
p.symbol && p.kind === SyntaxKind.PropertyAssignment &&
p.symbol.escapedName === keyPropertyName && isPossiblyDiscriminantValue(p.initializer));
const propType = propNode && getContextFreeTypeOfExpression((propNode as PropertyAssignment).initializer);
return propType && getConstituentTypeForKeyType(unionType, propType);
}
function isOrContainsMatchingReference(source: Node, target: Node) {
return isMatchingReference(source, target) || containsMatchingReference(source, target);
}
function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) {
if (expression.arguments) {
for (const argument of expression.arguments) {
if (isOrContainsMatchingReference(reference, argument) || optionalChainContainsReference(argument, reference)) {
return true;
}
}
}
if (
expression.expression.kind === SyntaxKind.PropertyAccessExpression &&
isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression)
) {
return true;
}
return false;
}
function getFlowNodeId(flow: FlowNode): number {
if (flow.id <= 0) {
flow.id = nextFlowId;
nextFlowId++;
}
return flow.id;
}
function typeMaybeAssignableTo(source: Type, target: Type) {
if (!(source.flags & TypeFlags.Union)) {
return isTypeAssignableTo(source, target);
}
for (const t of (source as UnionType).types) {
if (isTypeAssignableTo(t, target)) {
return true;
}
}
return false;
}
// Remove those constituent types of declaredType to which no constituent type of assignedType is assignable.
// For example, when a variable of type number | string | boolean is assigned a value of type number | boolean,
// we remove type string.
function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) {
if (declaredType === assignedType) {
return declaredType;
}
if (assignedType.flags & TypeFlags.Never) {
return assignedType;
}
const key = `A${getTypeId(declaredType)},${getTypeId(assignedType)}`;
return getCachedType(key) ?? setCachedType(key, getAssignmentReducedTypeWorker(declaredType, assignedType));
}
function getAssignmentReducedTypeWorker(declaredType: UnionType, assignedType: Type) {
const filteredType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
// Ensure that we narrow to fresh types if the assignment is a fresh boolean literal type.
const reducedType = assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType) ? mapType(filteredType, getFreshTypeOfLiteralType) : filteredType;
// Our crude heuristic produces an invalid result in some cases: see GH#26130.
// For now, when that happens, we give up and don't narrow at all. (This also
// means we'll never narrow for erroneous assignments where the assigned type
// is not assignable to the declared type.)
return isTypeAssignableTo(assignedType, reducedType) ? reducedType : declaredType;
}
function isFunctionObjectType(type: ObjectType): boolean {
if (getObjectFlags(type) & ObjectFlags.EvolvingArray) {
return false;
}
// We do a quick check for a "bind" property before performing the more expensive subtype
// check. This gives us a quicker out in the common case where an object type is not a function.
const resolved = resolveStructuredTypeMembers(type);
return !!(resolved.callSignatures.length || resolved.constructSignatures.length ||
resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType));
}
function getTypeFacts(type: Type, mask: TypeFacts): TypeFacts {
return getTypeFactsWorker(type, mask) & mask;
}
function hasTypeFacts(type: Type, mask: TypeFacts): boolean {
return getTypeFacts(type, mask) !== 0;
}
function getTypeFactsWorker(type: Type, callerOnlyNeeds: TypeFacts): TypeFacts {
if (type.flags & (TypeFlags.Intersection | TypeFlags.Instantiable)) {
type = getBaseConstraintOfType(type) || unknownType;
}
const flags = type.flags;
if (flags & (TypeFlags.String | TypeFlags.StringMapping)) {
return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts;
}
if (flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral)) {
const isEmpty = flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === "";
return strictNullChecks ?
isEmpty ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts :
isEmpty ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts;
}
if (flags & (TypeFlags.Number | TypeFlags.Enum)) {
return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts;
}
if (flags & TypeFlags.NumberLiteral) {
const isZero = (type as NumberLiteralType).value === 0;
return strictNullChecks ?
isZero ? TypeFacts.ZeroNumberStrictFacts : TypeFacts.NonZeroNumberStrictFacts :
isZero ? TypeFacts.ZeroNumberFacts : TypeFacts.NonZeroNumberFacts;
}
if (flags & TypeFlags.BigInt) {
return strictNullChecks ? TypeFacts.BigIntStrictFacts : TypeFacts.BigIntFacts;
}
if (flags & TypeFlags.BigIntLiteral) {
const isZero = isZeroBigInt(type as BigIntLiteralType);
return strictNullChecks ?
isZero ? TypeFacts.ZeroBigIntStrictFacts : TypeFacts.NonZeroBigIntStrictFacts :
isZero ? TypeFacts.ZeroBigIntFacts : TypeFacts.NonZeroBigIntFacts;
}
if (flags & TypeFlags.Boolean) {
return strictNullChecks ? TypeFacts.BooleanStrictFacts : TypeFacts.BooleanFacts;
}
if (flags & TypeFlags.BooleanLike) {
return strictNullChecks ?
(type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts :
(type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
}
if (flags & TypeFlags.Object) {
const possibleFacts = strictNullChecks
? TypeFacts.EmptyObjectStrictFacts | TypeFacts.FunctionStrictFacts | TypeFacts.ObjectStrictFacts
: TypeFacts.EmptyObjectFacts | TypeFacts.FunctionFacts | TypeFacts.ObjectFacts;
if ((callerOnlyNeeds & possibleFacts) === 0) {
// If the caller doesn't care about any of the facts that we could possibly produce,
// return zero so we can skip resolving members.
return 0;
}
return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ?
strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts :
isFunctionObjectType(type as ObjectType) ?
strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
}
if (flags & TypeFlags.Void) {
return TypeFacts.VoidFacts;
}
if (flags & TypeFlags.Undefined) {
return TypeFacts.UndefinedFacts;
}
if (flags & TypeFlags.Null) {
return TypeFacts.NullFacts;
}
if (flags & TypeFlags.ESSymbolLike) {
return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts;
}
if (flags & TypeFlags.NonPrimitive) {
return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
}
if (flags & TypeFlags.Never) {
return TypeFacts.None;
}
if (flags & TypeFlags.Union) {
return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFactsWorker(t, callerOnlyNeeds), TypeFacts.None);
}
if (flags & TypeFlags.Intersection) {
return getIntersectionTypeFacts(type as IntersectionType, callerOnlyNeeds);
}
return TypeFacts.UnknownFacts;
}
function getIntersectionTypeFacts(type: IntersectionType, callerOnlyNeeds: TypeFacts): TypeFacts {
// When an intersection contains a primitive type we ignore object type constituents as they are
// presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type.
const ignoreObjects = maybeTypeOfKind(type, TypeFlags.Primitive);
// When computing the type facts of an intersection type, certain type facts are computed as `and`
// and others are computed as `or`.
let oredFacts = TypeFacts.None;
let andedFacts = TypeFacts.All;
for (const t of type.types) {
if (!(ignoreObjects && t.flags & TypeFlags.Object)) {
const f = getTypeFactsWorker(t, callerOnlyNeeds);
oredFacts |= f;
andedFacts &= f;
}
}
return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask;
}
function getTypeWithFacts(type: Type, include: TypeFacts) {
return filterType(type, t => hasTypeFacts(t, include));
}
// This function is similar to getTypeWithFacts, except that in strictNullChecks mode it replaces type
// unknown with the union {} | null | undefined (and reduces that accordingly), and it intersects remaining
// instantiable types with {}, {} | null, or {} | undefined in order to remove null and/or undefined.
function getAdjustedTypeWithFacts(type: Type, facts: TypeFacts) {
const reduced = recombineUnknownType(getTypeWithFacts(strictNullChecks && type.flags & TypeFlags.Unknown ? unknownUnionType : type, facts));
if (strictNullChecks) {
switch (facts) {
case TypeFacts.NEUndefined:
return removeNullableByIntersection(reduced, TypeFacts.EQUndefined, TypeFacts.EQNull, TypeFacts.IsNull, nullType);
case TypeFacts.NENull:
return removeNullableByIntersection(reduced, TypeFacts.EQNull, TypeFacts.EQUndefined, TypeFacts.IsUndefined, undefinedType);
case TypeFacts.NEUndefinedOrNull:
case TypeFacts.Truthy:
return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefinedOrNull) ? getGlobalNonNullableTypeInstantiation(t) : t);
}
}
return reduced;
}
function removeNullableByIntersection(type: Type, targetFacts: TypeFacts, otherFacts: TypeFacts, otherIncludesFacts: TypeFacts, otherType: Type) {
const facts = getTypeFacts(type, TypeFacts.EQUndefined | TypeFacts.EQNull | TypeFacts.IsUndefined | TypeFacts.IsNull);
// Simply return the type if it never compares equal to the target nullable.
if (!(facts & targetFacts)) {
return type;
}
// By default we intersect with a union of {} and the opposite nullable.
const emptyAndOtherUnion = getUnionType([emptyObjectType, otherType]);
// For each constituent type that can compare equal to the target nullable, intersect with the above union
// if the type doesn't already include the opppsite nullable and the constituent can compare equal to the
// opposite nullable; otherwise, just intersect with {}.
return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, !(facts & otherIncludesFacts) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t);
}
function recombineUnknownType(type: Type) {
return type === unknownUnionType ? unknownType : type;
}
function getTypeWithDefault(type: Type, defaultExpression: Expression) {
return defaultExpression ?
getUnionType([getNonUndefinedType(type), getTypeOfExpression(defaultExpression)]) :
type;
}
function getTypeOfDestructuredProperty(type: Type, name: PropertyName) {
const nameType = getLiteralTypeFromPropertyName(name);
if (!isTypeUsableAsPropertyName(nameType)) return errorType;
const text = getPropertyNameFromType(nameType);
return getTypeOfPropertyOfType(type, text) || includeUndefinedInIndexSignature(getApplicableIndexInfoForName(type, text)?.type) || errorType;
}
function getTypeOfDestructuredArrayElement(type: Type, index: number) {
return everyType(type, isTupleLikeType) && getTupleElementType(type, index) ||
includeUndefinedInIndexSignature(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined)) ||
errorType;
}
function includeUndefinedInIndexSignature(type: Type | undefined): Type | undefined {
if (!type) return type;
return compilerOptions.noUncheckedIndexedAccess ?
getUnionType([type, missingType]) :
type;
}
function getTypeOfDestructuredSpreadExpression(type: Type) {
return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType);
}
function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type {
const isDestructuringDefaultAssignment = node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) ||
node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent);
return isDestructuringDefaultAssignment ?
getTypeWithDefault(getAssignedType(node), node.right) :
getTypeOfExpression(node.right);
}
function isDestructuringAssignmentTarget(parent: Node) {
return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent ||
parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent;
}
function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type {
return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element));
}
function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type {
return getTypeOfDestructuredSpreadExpression(getAssignedType(node.parent as ArrayLiteralExpression));
}
function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment): Type {
return getTypeOfDestructuredProperty(getAssignedType(node.parent), node.name);
}
function getAssignedTypeOfShorthandPropertyAssignment(node: ShorthandPropertyAssignment): Type {
return getTypeWithDefault(getAssignedTypeOfPropertyAssignment(node), node.objectAssignmentInitializer!);
}
function getAssignedType(node: Expression): Type {
const { parent } = node;
switch (parent.kind) {
case SyntaxKind.ForInStatement:
return stringType;
case SyntaxKind.ForOfStatement:
return checkRightHandSideOfForOf(parent as ForOfStatement) || errorType;
case SyntaxKind.BinaryExpression:
return getAssignedTypeOfBinaryExpression(parent as BinaryExpression);
case SyntaxKind.DeleteExpression:
return undefinedType;
case SyntaxKind.ArrayLiteralExpression:
return getAssignedTypeOfArrayLiteralElement(parent as ArrayLiteralExpression, node);
case SyntaxKind.SpreadElement:
return getAssignedTypeOfSpreadExpression(parent as SpreadElement);
case SyntaxKind.PropertyAssignment:
return getAssignedTypeOfPropertyAssignment(parent as PropertyAssignment);
case SyntaxKind.ShorthandPropertyAssignment:
return getAssignedTypeOfShorthandPropertyAssignment(parent as ShorthandPropertyAssignment);
}
return errorType;
}
function getInitialTypeOfBindingElement(node: BindingElement): Type {
const pattern = node.parent;
const parentType = getInitialType(pattern.parent as VariableDeclaration | BindingElement);
const type = pattern.kind === SyntaxKind.ObjectBindingPattern ?
getTypeOfDestructuredProperty(parentType, node.propertyName || node.name as Identifier) :
!node.dotDotDotToken ?
getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) :
getTypeOfDestructuredSpreadExpression(parentType);
return getTypeWithDefault(type, node.initializer!);
}
function getTypeOfInitializer(node: Expression) {
// Return the cached type if one is available. If the type of the variable was inferred
// from its initializer, we'll already have cached the type. Otherwise we compute it now
// without caching such that transient types are reflected.
const links = getNodeLinks(node);
return links.resolvedType || getTypeOfExpression(node);
}
function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) {
if (node.initializer) {
return getTypeOfInitializer(node.initializer);
}
if (node.parent.parent.kind === SyntaxKind.ForInStatement) {
return stringType;
}
if (node.parent.parent.kind === SyntaxKind.ForOfStatement) {
return checkRightHandSideOfForOf(node.parent.parent) || errorType;
}
return errorType;
}
function getInitialType(node: VariableDeclaration | BindingElement) {
return node.kind === SyntaxKind.VariableDeclaration ?
getInitialTypeOfVariableDeclaration(node) :
getInitialTypeOfBindingElement(node);
}
function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) {
return node.kind === SyntaxKind.VariableDeclaration && (node as VariableDeclaration).initializer &&
isEmptyArrayLiteral((node as VariableDeclaration).initializer!) ||
node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression &&
isEmptyArrayLiteral((node.parent as BinaryExpression).right);
}
function getReferenceCandidate(node: Expression): Expression {
switch (node.kind) {
case SyntaxKind.ParenthesizedExpression:
return getReferenceCandidate((node as ParenthesizedExpression).expression);
case SyntaxKind.BinaryExpression:
switch ((node as BinaryExpression).operatorToken.kind) {
case SyntaxKind.EqualsToken:
case SyntaxKind.BarBarEqualsToken:
case SyntaxKind.AmpersandAmpersandEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
return getReferenceCandidate((node as BinaryExpression).left);
case SyntaxKind.CommaToken:
return getReferenceCandidate((node as BinaryExpression).right);
}
}
return node;
}
function getReferenceRoot(node: Node): Node {
const { parent } = node;
return parent.kind === SyntaxKind.ParenthesizedExpression ||
parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && (parent as BinaryExpression).left === node ||
parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken && (parent as BinaryExpression).right === node ?
getReferenceRoot(parent) : node;
}
function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) {
if (clause.kind === SyntaxKind.CaseClause) {
return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression));
}
return neverType;
}
function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] {
const links = getNodeLinks(switchStatement);
if (!links.switchTypes) {
links.switchTypes = [];
for (const clause of switchStatement.caseBlock.clauses) {
links.switchTypes.push(getTypeOfSwitchClause(clause));
}
}
return links.switchTypes;
}
// Get the type names from all cases in a switch on `typeof`. The default clause and/or duplicate type names are
// represented as undefined. Return undefined if one or more case clause expressions are not string literals.
function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] | undefined {
if (some(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.CaseClause && !isStringLiteralLike(clause.expression))) {
return undefined;
}
const witnesses: (string | undefined)[] = [];
for (const clause of switchStatement.caseBlock.clauses) {
const text = clause.kind === SyntaxKind.CaseClause ? (clause.expression as StringLiteralLike).text : undefined;
witnesses.push(text && !contains(witnesses, text) ? text : undefined);
}
return witnesses;
}
function eachTypeContainedIn(source: Type, types: Type[]) {
return source.flags & TypeFlags.Union ? !forEach((source as UnionType).types, t => !contains(types, t)) : contains(types, source);
}
function isTypeSubsetOf(source: Type, target: Type) {
return !!(source === target || source.flags & TypeFlags.Never || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType));
}
function isTypeSubsetOfUnion(source: Type, target: UnionType) {
if (source.flags & TypeFlags.Union) {
for (const t of (source as UnionType).types) {
if (!containsType(target.types, t)) {
return false;
}
}
return true;
}
if (source.flags & TypeFlags.EnumLike && getBaseTypeOfEnumLikeType(source as LiteralType) === target) {
return true;
}
return containsType(target.types, source);
}
function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined {
return type.flags & TypeFlags.Union ? forEach((type as UnionType).types, f) : f(type);
}
function someType(type: Type, f: (t: Type) => boolean): boolean {
return type.flags & TypeFlags.Union ? some((type as UnionType).types, f) : f(type);
}
function everyType(type: Type, f: (t: Type) => boolean): boolean {
return type.flags & TypeFlags.Union ? every((type as UnionType).types, f) : f(type);
}
function everyContainedType(type: Type, f: (t: Type) => boolean): boolean {
return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type);
}
function filterType(type: Type, f: (t: Type) => boolean): Type {
if (type.flags & TypeFlags.Union) {
const types = (type as UnionType).types;
const filtered = filter(types, f);
if (filtered === types) {
return type;
}
const origin = (type as UnionType).origin;
let newOrigin: Type | undefined;
if (origin && origin.flags & TypeFlags.Union) {
// If the origin type is a (denormalized) union type, filter its non-union constituents. If that ends
// up removing a smaller number of types than in the normalized constituent set (meaning some of the
// filtered types are within nested unions in the origin), then we can't construct a new origin type.
// Otherwise, if we have exactly one type left in the origin set, return that as the filtered type.
// Otherwise, construct a new filtered origin type.
const originTypes = (origin as UnionType).types;
const originFiltered = filter(originTypes, t => !!(t.flags & TypeFlags.Union) || f(t));
if (originTypes.length - originFiltered.length === types.length - filtered.length) {
if (originFiltered.length === 1) {
return originFiltered[0];
}
newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered);
}
}
// filtering could remove intersections so `ContainsIntersections` might be forwarded "incorrectly"
// it is purely an optimization hint so there is no harm in accidentally forwarding it
return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags & (ObjectFlags.PrimitiveUnion | ObjectFlags.ContainsIntersections), /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin);
}
return type.flags & TypeFlags.Never || f(type) ? type : neverType;
}
function removeType(type: Type, targetType: Type) {
return filterType(type, t => t !== targetType);
}
function countTypes(type: Type) {
return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1;
}
// Apply a mapping function to a type and return the resulting type. If the source type
// is a union type, the mapping function is applied to each constituent type and a union
// of the resulting types is returned.
function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type;
function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined;
function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined {
if (type.flags & TypeFlags.Never) {
return type;
}
if (!(type.flags & TypeFlags.Union)) {
return mapper(type);
}
const origin = (type as UnionType).origin;
const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types;
let mappedTypes: Type[] | undefined;
let changed = false;
for (const t of types) {
const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions) : mapper(t);
changed ||= t !== mapped;
if (mapped) {
if (!mappedTypes) {
mappedTypes = [mapped];
}
else {
mappedTypes.push(mapped);
}
}
}
return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type;
}
function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) {
return type.flags & TypeFlags.Union && aliasSymbol ?
getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments) :
mapType(type, mapper);
}
function extractTypesOfKind(type: Type, kind: TypeFlags) {
return filterType(type, t => (t.flags & kind) !== 0);
}
// Return a new type in which occurrences of the string, number and bigint primitives and placeholder template
// literal types in typeWithPrimitives have been replaced with occurrences of compatible and more specific types
// from typeWithLiterals. This is essentially a limited form of intersection between the two types. We avoid a
// true intersection because it is more costly and, when applied to union types, generates a large number of
// types we don't actually care about.
function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) {
if (
maybeTypeOfKind(typeWithPrimitives, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.Number | TypeFlags.BigInt) &&
maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.NumberLiteral | TypeFlags.BigIntLiteral)
) {
return mapType(typeWithPrimitives, t =>
t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) :
isPatternLiteralType(t) && !maybeTypeOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? extractTypesOfKind(typeWithLiterals, TypeFlags.StringLiteral) :
t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) :
t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t);
}
return typeWithPrimitives;
}
function isIncomplete(flowType: FlowType) {
return flowType.flags === 0;
}
function getTypeFromFlowType(flowType: FlowType) {
return flowType.flags === 0 ? flowType.type : flowType as Type;
}
function createFlowType(type: Type, incomplete: boolean): FlowType {
return incomplete ? { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type } : type;
}
// An evolving array type tracks the element types that have so far been seen in an
// 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving
// array types are ultimately converted into manifest array types (using getFinalArrayType)
// and never escape the getFlowTypeOfReference function.
function createEvolvingArrayType(elementType: Type): EvolvingArrayType {
const result = createObjectType(ObjectFlags.EvolvingArray) as EvolvingArrayType;
result.elementType = elementType;
return result;
}
function getEvolvingArrayType(elementType: Type): EvolvingArrayType {
return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType));
}
// When adding evolving array element types we do not perform subtype reduction. Instead,
// we defer subtype reduction until the evolving array type is finalized into a manifest
// array type.
function addEvolvingArrayElementType(evolvingArrayType: EvolvingArrayType, node: Expression): EvolvingArrayType {
const elementType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(getContextFreeTypeOfExpression(node)));
return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType]));
}
function createFinalArrayType(elementType: Type) {
return elementType.flags & TypeFlags.Never ?
autoArrayType :
createArrayType(
elementType.flags & TypeFlags.Union ?
getUnionType((elementType as UnionType).types, UnionReduction.Subtype) :
elementType,
);
}
// We perform subtype reduction upon obtaining the final array type from an evolving array type.
function getFinalArrayType(evolvingArrayType: EvolvingArrayType): Type {
return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType));
}
function finalizeEvolvingArrayType(type: Type): Type {
return getObjectFlags(type) & ObjectFlags.EvolvingArray ? getFinalArrayType(type as EvolvingArrayType) : type;
}
function getElementTypeOfEvolvingArrayType(type: Type) {
return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (type as EvolvingArrayType).elementType : neverType;
}
function isEvolvingArrayTypeList(types: Type[]) {
let hasEvolvingArrayType = false;
for (const t of types) {
if (!(t.flags & TypeFlags.Never)) {
if (!(getObjectFlags(t) & ObjectFlags.EvolvingArray)) {
return false;
}
hasEvolvingArrayType = true;
}
}
return hasEvolvingArrayType;
}
// Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or
// 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type.
function isEvolvingArrayOperationTarget(node: Node) {
const root = getReferenceRoot(node);
const parent = root.parent;
const isLengthPushOrUnshift = isPropertyAccessExpression(parent) && (
parent.name.escapedText === "length" ||
parent.parent.kind === SyntaxKind.CallExpression
&& isIdentifier(parent.name)
&& isPushOrUnshiftIdentifier(parent.name)
);
const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression &&
(parent as ElementAccessExpression).expression === root &&
parent.parent.kind === SyntaxKind.BinaryExpression &&
(parent.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
(parent.parent as BinaryExpression).left === parent &&
!isAssignmentTarget(parent.parent) &&
isTypeAssignableToKind(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression), TypeFlags.NumberLike);
return isLengthPushOrUnshift || isElementAssignment;
}
function isDeclarationWithExplicitTypeAnnotation(node: Declaration) {
return (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isParameter(node)) &&
!!(getEffectiveTypeAnnotationNode(node) ||
isInJSFile(node) && hasInitializer(node) && node.initializer && isFunctionExpressionOrArrowFunction(node.initializer) && getEffectiveReturnTypeNode(node.initializer));
}
function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) {
symbol = resolveSymbol(symbol);
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) {
return getTypeOfSymbol(symbol);
}
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
if (getCheckFlags(symbol) & CheckFlags.Mapped) {
const origin = (symbol as MappedSymbol).links.syntheticOrigin;
if (origin && getExplicitTypeOfSymbol(origin)) {
return getTypeOfSymbol(symbol);
}
}
const declaration = symbol.valueDeclaration;
if (declaration) {
if (isDeclarationWithExplicitTypeAnnotation(declaration)) {
return getTypeOfSymbol(symbol);
}
if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) {
const statement = declaration.parent.parent;
const expressionType = getTypeOfDottedName(statement.expression, /*diagnostic*/ undefined);
if (expressionType) {
const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf;
return checkIteratedTypeOrElementType(use, expressionType, undefinedType, /*errorNode*/ undefined);
}
}
if (diagnostic) {
addRelatedInfo(diagnostic, createDiagnosticForNode(declaration, Diagnostics._0_needs_an_explicit_type_annotation, symbolToString(symbol)));
}
}
}
}
// We require the dotted function name in an assertion expression to be comprised of identifiers
// that reference function, method, class or value module symbols; or variable, property or
// parameter symbols with declarations that have explicit type annotations. Such references are
// resolvable with no possibility of triggering circularities in control flow analysis.
function getTypeOfDottedName(node: Expression, diagnostic: Diagnostic | undefined): Type | undefined {
if (!(node.flags & NodeFlags.InWithStatement)) {
switch (node.kind) {
case SyntaxKind.Identifier:
const symbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(node as Identifier));
return getExplicitTypeOfSymbol(symbol, diagnostic);
case SyntaxKind.ThisKeyword:
return getExplicitThisType(node);
case SyntaxKind.SuperKeyword:
return checkSuperExpression(node);
case SyntaxKind.PropertyAccessExpression: {
const type = getTypeOfDottedName((node as PropertyAccessExpression).expression, diagnostic);
if (type) {
const name = (node as PropertyAccessExpression).name;
let prop: Symbol | undefined;
if (isPrivateIdentifier(name)) {
if (!type.symbol) {
return undefined;
}
prop = getPropertyOfType(type, getSymbolNameForPrivateIdentifier(type.symbol, name.escapedText));
}
else {
prop = getPropertyOfType(type, name.escapedText);
}
return prop && getExplicitTypeOfSymbol(prop, diagnostic);
}
return undefined;
}
case SyntaxKind.ParenthesizedExpression:
return getTypeOfDottedName((node as ParenthesizedExpression).expression, diagnostic);
}
}
}
function getEffectsSignature(node: CallExpression | InstanceofExpression) {
const links = getNodeLinks(node);
let signature = links.effectsSignature;
if (signature === undefined) {
// A call expression parented by an expression statement is a potential assertion. Other call
// expressions are potential type predicate function calls. In order to avoid triggering
// circularities in control flow analysis, we use getTypeOfDottedName when resolving the call
// target expression of an assertion.
let funcType: Type | undefined;
if (isBinaryExpression(node)) {
const rightType = checkNonNullExpression(node.right);
funcType = getSymbolHasInstanceMethodOfObjectType(rightType);
}
else if (node.parent.kind === SyntaxKind.ExpressionStatement) {
funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined);
}
else if (node.expression.kind !== SyntaxKind.SuperKeyword) {
if (isOptionalChain(node)) {
funcType = checkNonNullType(
getOptionalExpressionType(checkExpression(node.expression), node.expression),
node.expression,
);
}
else {
funcType = checkNonNullExpression(node.expression);
}
}
const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call);
const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] :
some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) :
undefined;
signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature;
}
return signature === unknownSignature ? undefined : signature;
}
function hasTypePredicateOrNeverReturnType(signature: Signature) {
return !!(getTypePredicateOfSignature(signature) ||
signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never);
}
function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) {
if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) {
return callExpression.arguments[predicate.parameterIndex];
}
const invokedExpression = skipParentheses(callExpression.expression);
return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined;
}
function reportFlowControlError(node: Node) {
const block = findAncestor(node, isFunctionOrModuleBlock) as Block | ModuleBlock | SourceFile;
const sourceFile = getSourceFileOfNode(node);
const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos);
diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis));
}
function isReachableFlowNode(flow: FlowNode) {
const result = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ false);
lastFlowNode = flow;
lastFlowNodeReachable = result;
return result;
}
function isFalseExpression(expr: Expression): boolean {
const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true);
return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && (
(node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node as BinaryExpression).left) || isFalseExpression((node as BinaryExpression).right)) ||
(node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node as BinaryExpression).left) && isFalseExpression((node as BinaryExpression).right)
);
}
function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean {
while (true) {
if (flow === lastFlowNode) {
return lastFlowNodeReachable;
}
const flags = flow.flags;
if (flags & FlowFlags.Shared) {
if (!noCacheCheck) {
const id = getFlowNodeId(flow);
const reachable = flowNodeReachable[id];
return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ true));
}
noCacheCheck = false;
}
if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) {
flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation).antecedent;
}
else if (flags & FlowFlags.Call) {
const signature = getEffectsSignature((flow as FlowCall).node);
if (signature) {
const predicate = getTypePredicateOfSignature(signature);
if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier && !predicate.type) {
const predicateArgument = (flow as FlowCall).node.arguments[predicate.parameterIndex];
if (predicateArgument && isFalseExpression(predicateArgument)) {
return false;
}
}
if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) {
return false;
}
}
flow = (flow as FlowCall).antecedent;
}
else if (flags & FlowFlags.BranchLabel) {
// A branching point is reachable if any branch is reachable.
return some((flow as FlowLabel).antecedent, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false));
}
else if (flags & FlowFlags.LoopLabel) {
const antecedents = (flow as FlowLabel).antecedent;
if (antecedents === undefined || antecedents.length === 0) {
return false;
}
// A loop is reachable if the control flow path that leads to the top is reachable.
flow = antecedents[0];
}
else if (flags & FlowFlags.SwitchClause) {
// The control flow path representing an unmatched value in a switch statement with
// no default clause is unreachable if the switch statement is exhaustive.
const data = (flow as FlowSwitchClause).node;
if (data.clauseStart === data.clauseEnd && isExhaustiveSwitchStatement(data.switchStatement)) {
return false;
}
flow = (flow as FlowSwitchClause).antecedent;
}
else if (flags & FlowFlags.ReduceLabel) {
// Cache is unreliable once we start adjusting labels
lastFlowNode = undefined;
const target = (flow as FlowReduceLabel).node.target;
const saveAntecedents = target.antecedent;
target.antecedent = (flow as FlowReduceLabel).node.antecedents;
const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false);
target.antecedent = saveAntecedents;
return result;
}
else {
return !(flags & FlowFlags.Unreachable);
}
}
}
// Return true if the given flow node is preceded by a 'super(...)' call in every possible code path
// leading to the node.
function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean {
while (true) {
const flags = flow.flags;
if (flags & FlowFlags.Shared) {
if (!noCacheCheck) {
const id = getFlowNodeId(flow);
const postSuper = flowNodePostSuper[id];
return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true));
}
noCacheCheck = false;
}
if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) {
flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation | FlowSwitchClause).antecedent;
}
else if (flags & FlowFlags.Call) {
if ((flow as FlowCall).node.expression.kind === SyntaxKind.SuperKeyword) {
return true;
}
flow = (flow as FlowCall).antecedent;
}
else if (flags & FlowFlags.BranchLabel) {
// A branching point is post-super if every branch is post-super.
return every((flow as FlowLabel).antecedent, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false));
}
else if (flags & FlowFlags.LoopLabel) {
// A loop is post-super if the control flow path that leads to the top is post-super.
flow = (flow as FlowLabel).antecedent![0];
}
else if (flags & FlowFlags.ReduceLabel) {
const target = (flow as FlowReduceLabel).node.target;
const saveAntecedents = target.antecedent;
target.antecedent = (flow as FlowReduceLabel).node.antecedents;
const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false);
target.antecedent = saveAntecedents;
return result;
}
else {
// Unreachable nodes are considered post-super to silence errors
return !!(flags & FlowFlags.Unreachable);
}
}
}
function isConstantReference(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.ThisKeyword:
return true;
case SyntaxKind.Identifier:
if (!isThisInTypeQuery(node)) {
const symbol = getResolvedSymbol(node as Identifier);
return isConstantVariable(symbol)
|| isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol)
|| !!symbol.valueDeclaration && isFunctionExpression(symbol.valueDeclaration);
}
break;
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
// The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here.
return isConstantReference((node as AccessExpression).expression) && isReadonlySymbol(getNodeLinks(node).resolvedSymbol || unknownSymbol);
case SyntaxKind.ObjectBindingPattern:
case SyntaxKind.ArrayBindingPattern:
const rootDeclaration = getRootDeclaration(node.parent);
return isParameter(rootDeclaration) || isCatchClauseVariableDeclaration(rootDeclaration)
? !isSomeSymbolAssigned(rootDeclaration)
: isVariableDeclaration(rootDeclaration) && isVarConstLike(rootDeclaration);
}
return false;
}
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = tryCast(reference, canHaveFlowNode)?.flowNode) {
let key: string | undefined;
let isKeySet = false;
let flowDepth = 0;
if (flowAnalysisDisabled) {
return errorType;
}
if (!flowNode) {
return declaredType;
}
flowInvocationCount++;
const sharedFlowStart = sharedFlowCount;
const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode));
sharedFlowCount = sharedFlowStart;
// When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation,
// we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations
// on empty arrays are possible without implicit any errors and new element types can be inferred without
// type mismatch errors.
const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType);
if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && !(resultType.flags & TypeFlags.Never) && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
return declaredType;
}
return resultType;
function getOrSetCacheKey() {
if (isKeySet) {
return key;
}
isKeySet = true;
return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer);
}
function getTypeAtFlowNode(flow: FlowNode): FlowType {
if (flowDepth === 2000) {
// We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error
// and disable further control flow analysis in the containing function or module body.
tracing?.instant(tracing.Phase.CheckTypes, "getTypeAtFlowNode_DepthLimit", { flowId: flow.id });
flowAnalysisDisabled = true;
reportFlowControlError(reference);
return errorType;
}
flowDepth++;
let sharedFlow: FlowNode | undefined;
while (true) {
const flags = flow.flags;
if (flags & FlowFlags.Shared) {
// We cache results of flow type resolution for shared nodes that were previously visited in
// the same getFlowTypeOfReference invocation. A node is considered shared when it is the
// antecedent of more than one node.
for (let i = sharedFlowStart; i < sharedFlowCount; i++) {
if (sharedFlowNodes[i] === flow) {
flowDepth--;
return sharedFlowTypes[i];
}
}
sharedFlow = flow;
}
let type: FlowType | undefined;
if (flags & FlowFlags.Assignment) {
type = getTypeAtFlowAssignment(flow as FlowAssignment);
if (!type) {
flow = (flow as FlowAssignment).antecedent;
continue;
}
}
else if (flags & FlowFlags.Call) {
type = getTypeAtFlowCall(flow as FlowCall);
if (!type) {
flow = (flow as FlowCall).antecedent;
continue;
}
}
else if (flags & FlowFlags.Condition) {
type = getTypeAtFlowCondition(flow as FlowCondition);
}
else if (flags & FlowFlags.SwitchClause) {
type = getTypeAtSwitchClause(flow as FlowSwitchClause);
}
else if (flags & FlowFlags.Label) {
if ((flow as FlowLabel).antecedent!.length === 1) {
flow = (flow as FlowLabel).antecedent![0];
continue;
}
type = flags & FlowFlags.BranchLabel ?
getTypeAtFlowBranchLabel(flow as FlowLabel) :
getTypeAtFlowLoopLabel(flow as FlowLabel);
}
else if (flags & FlowFlags.ArrayMutation) {
type = getTypeAtFlowArrayMutation(flow as FlowArrayMutation);
if (!type) {
flow = (flow as FlowArrayMutation).antecedent;
continue;
}
}
else if (flags & FlowFlags.ReduceLabel) {
const target = (flow as FlowReduceLabel).node.target;
const saveAntecedents = target.antecedent;
target.antecedent = (flow as FlowReduceLabel).node.antecedents;
type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent);
target.antecedent = saveAntecedents;
}
else if (flags & FlowFlags.Start) {
// Check if we should continue with the control flow of the containing function.
const container = (flow as FlowStart).node;
if (
container && container !== flowContainer &&
reference.kind !== SyntaxKind.PropertyAccessExpression &&
reference.kind !== SyntaxKind.ElementAccessExpression &&
!(reference.kind === SyntaxKind.ThisKeyword && container.kind !== SyntaxKind.ArrowFunction)
) {
flow = container.flowNode!;
continue;
}
// At the top of the flow we have the initial type.
type = initialType;
}
else {
// Unreachable code errors are reported in the binding phase. Here we
// simply return the non-auto declared type to reduce follow-on errors.
type = convertAutoToAny(declaredType);
}
if (sharedFlow) {
// Record visited node and the associated type in the cache.
sharedFlowNodes[sharedFlowCount] = sharedFlow;
sharedFlowTypes[sharedFlowCount] = type;
sharedFlowCount++;
}
flowDepth--;
return type;
}
}
function getInitialOrAssignedType(flow: FlowAssignment) {
const node = flow.node;
return getNarrowableTypeForReference(
node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ?
getInitialType(node as VariableDeclaration | BindingElement) :
getAssignedType(node),
reference,
);
}
function getTypeAtFlowAssignment(flow: FlowAssignment) {
const node = flow.node;
// Assignments only narrow the computed type if the declared type is a union type. Thus, we
// only need to evaluate the assigned type if the declared type is a union type.
if (isMatchingReference(reference, node)) {
if (!isReachableFlowNode(flow)) {
return unreachableNeverType;
}
if (getAssignmentTargetKind(node) === AssignmentKind.Compound) {
const flowType = getTypeAtFlowNode(flow.antecedent);
return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType));
}
if (declaredType === autoType || declaredType === autoArrayType) {
if (isEmptyArrayAssignment(node)) {
return getEvolvingArrayType(neverType);
}
const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow));
return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType;
}
const t = isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(declaredType) : declaredType;
if (t.flags & TypeFlags.Union) {
return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow));
}
return t;
}
// We didn't have a direct match. However, if the reference is a dotted name, this
// may be an assignment to a left hand part of the reference. For example, for a
// reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case,
// return the declared type.
if (containsMatchingReference(reference, node)) {
if (!isReachableFlowNode(flow)) {
return unreachableNeverType;
}
// A matching dotted name might also be an expando property on a function *expression*,
// in which case we continue control flow analysis back to the function's declaration
if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConstLike(node))) {
const init = getDeclaredExpandoInitializer(node);
if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) {
return getTypeAtFlowNode(flow.antecedent);
}
}
return declaredType;
}
// for (const _ in ref) acts as a nonnull on ref
if (
isVariableDeclaration(node) &&
node.parent.parent.kind === SyntaxKind.ForInStatement &&
(isMatchingReference(reference, node.parent.parent.expression) || optionalChainContainsReference(node.parent.parent.expression, reference))
) {
return getNonNullableTypeIfNeeded(finalizeEvolvingArrayType(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent))));
}
// Assignment doesn't affect reference
return undefined;
}
function narrowTypeByAssertion(type: Type, expr: Expression): Type {
const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true);
if (node.kind === SyntaxKind.FalseKeyword) {
return unreachableNeverType;
}
if (node.kind === SyntaxKind.BinaryExpression) {
if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
return narrowTypeByAssertion(narrowTypeByAssertion(type, (node as BinaryExpression).left), (node as BinaryExpression).right);
}
if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken) {
return getUnionType([narrowTypeByAssertion(type, (node as BinaryExpression).left), narrowTypeByAssertion(type, (node as BinaryExpression).right)]);
}
}
return narrowType(type, node, /*assumeTrue*/ true);
}
function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined {
const signature = getEffectsSignature(flow.node);
if (signature) {
const predicate = getTypePredicateOfSignature(signature);
if (predicate && (predicate.kind === TypePredicateKind.AssertsThis || predicate.kind === TypePredicateKind.AssertsIdentifier)) {
const flowType = getTypeAtFlowNode(flow.antecedent);
const type = finalizeEvolvingArrayType(getTypeFromFlowType(flowType));
const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) :
predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) :
type;
return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType));
}
if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) {
return unreachableNeverType;
}
}
return undefined;
}
function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined {
if (declaredType === autoType || declaredType === autoArrayType) {
const node = flow.node;
const expr = node.kind === SyntaxKind.CallExpression ?
(node.expression as PropertyAccessExpression).expression :
(node.left as ElementAccessExpression).expression;
if (isMatchingReference(reference, getReferenceCandidate(expr))) {
const flowType = getTypeAtFlowNode(flow.antecedent);
const type = getTypeFromFlowType(flowType);
if (getObjectFlags(type) & ObjectFlags.EvolvingArray) {
let evolvedType = type as EvolvingArrayType;
if (node.kind === SyntaxKind.CallExpression) {
for (const arg of node.arguments) {
evolvedType = addEvolvingArrayElementType(evolvedType, arg);
}
}
else {
// We must get the context free expression type so as to not recur in an uncached fashion on the LHS (which causes exponential blowup in compile time)
const indexType = getContextFreeTypeOfExpression((node.left as ElementAccessExpression).argumentExpression);
if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
evolvedType = addEvolvingArrayElementType(evolvedType, node.right);
}
}
return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType));
}
return flowType;
}
}
return undefined;
}
function getTypeAtFlowCondition(flow: FlowCondition): FlowType {
const flowType = getTypeAtFlowNode(flow.antecedent);
const type = getTypeFromFlowType(flowType);
if (type.flags & TypeFlags.Never) {
return flowType;
}
// If we have an antecedent type (meaning we're reachable in some way), we first
// attempt to narrow the antecedent type. If that produces the never type, and if
// the antecedent type is incomplete (i.e. a transient type in a loop), then we
// take the type guard as an indication that control *could* reach here once we
// have the complete type. We proceed by switching to the silent never type which
// doesn't report errors when operators are applied to it. Note that this is the
// *only* place a silent never type is ever generated.
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
const nonEvolvingType = finalizeEvolvingArrayType(type);
const narrowedType = narrowType(nonEvolvingType, flow.node, assumeTrue);
if (narrowedType === nonEvolvingType) {
return flowType;
}
return createFlowType(narrowedType, isIncomplete(flowType));
}
function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType {
const expr = skipParentheses(flow.node.switchStatement.expression);
const flowType = getTypeAtFlowNode(flow.antecedent);
let type = getTypeFromFlowType(flowType);
if (isMatchingReference(reference, expr)) {
type = narrowTypeBySwitchOnDiscriminant(type, flow.node);
}
else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
type = narrowTypeBySwitchOnTypeOf(type, flow.node);
}
else if (expr.kind === SyntaxKind.TrueKeyword) {
type = narrowTypeBySwitchOnTrue(type, flow.node);
}
else {
if (strictNullChecks) {
if (optionalChainContainsReference(expr, reference)) {
type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never)));
}
else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) {
type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined"));
}
}
const access = getDiscriminantPropertyAccess(expr, type);
if (access) {
type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.node);
}
}
return createFlowType(type, isIncomplete(flowType));
}
function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
const antecedentTypes: Type[] = [];
let subtypeReduction = false;
let seenIncomplete = false;
let bypassFlow: FlowSwitchClause | undefined;
for (const antecedent of flow.antecedent!) {
if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).node.clauseStart === (antecedent as FlowSwitchClause).node.clauseEnd) {
// The antecedent is the bypass branch of a potentially exhaustive switch statement.
bypassFlow = antecedent as FlowSwitchClause;
continue;
}
const flowType = getTypeAtFlowNode(antecedent);
const type = getTypeFromFlowType(flowType);
// If the type at a particular antecedent path is the declared type and the
// reference is known to always be assigned (i.e. when declared and initial types
// are the same), there is no reason to process more antecedents since the only
// possible outcome is subtypes that will be removed in the final union type anyway.
if (type === declaredType && declaredType === initialType) {
return type;
}
pushIfUnique(antecedentTypes, type);
// If an antecedent type is not a subset of the declared type, we need to perform
// subtype reduction. This happens when a "foreign" type is injected into the control
// flow using the instanceof operator or a user defined type predicate.
if (!isTypeSubsetOf(type, initialType)) {
subtypeReduction = true;
}
if (isIncomplete(flowType)) {
seenIncomplete = true;
}
}
if (bypassFlow) {
const flowType = getTypeAtFlowNode(bypassFlow);
const type = getTypeFromFlowType(flowType);
// If the bypass flow contributes a type we haven't seen yet and the switch statement
// isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase
// the risk of circularities, we only want to perform them when they make a difference.
if (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node.switchStatement)) {
if (type === declaredType && declaredType === initialType) {
return type;
}
antecedentTypes.push(type);
if (!isTypeSubsetOf(type, initialType)) {
subtypeReduction = true;
}
if (isIncomplete(flowType)) {
seenIncomplete = true;
}
}
}
return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete);
}
function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
// If we have previously computed the control flow type for the reference at
// this flow loop junction, return the cached type.
const id = getFlowNodeId(flow);
const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new Map());
const key = getOrSetCacheKey();
if (!key) {
// No cache key is generated when binding patterns are in unnarrowable situations
return declaredType;
}
const cached = cache.get(key);
if (cached) {
return cached;
}
// If this flow loop junction and reference are already being processed, return
// the union of the types computed for each branch so far, marked as incomplete.
// It is possible to see an empty array in cases where loops are nested and the
// back edge of the outer loop reaches an inner loop that is already being analyzed.
// In such cases we restart the analysis of the inner loop, which will then see
// a non-empty in-process array for the outer loop and eventually terminate because
// the first antecedent of a loop junction is always the non-looping control flow
// path that leads to the top.
for (let i = flowLoopStart; i < flowLoopCount; i++) {
if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) {
return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true);
}
}
// Add the flow loop junction and reference to the in-process stack and analyze
// each antecedent code path.
const antecedentTypes: Type[] = [];
let subtypeReduction = false;
let firstAntecedentType: FlowType | undefined;
for (const antecedent of flow.antecedent!) {
let flowType;
if (!firstAntecedentType) {
// The first antecedent of a loop junction is always the non-looping control
// flow path that leads to the top.
flowType = firstAntecedentType = getTypeAtFlowNode(antecedent);
}
else {
// All but the first antecedent are the looping control flow paths that lead
// back to the loop junction. We track these on the flow loop stack.
flowLoopNodes[flowLoopCount] = flow;
flowLoopKeys[flowLoopCount] = key;
flowLoopTypes[flowLoopCount] = antecedentTypes;
flowLoopCount++;
const saveFlowTypeCache = flowTypeCache;
flowTypeCache = undefined;
flowType = getTypeAtFlowNode(antecedent);
flowTypeCache = saveFlowTypeCache;
flowLoopCount--;
// If we see a value appear in the cache it is a sign that control flow analysis
// was restarted and completed by checkExpressionCached. We can simply pick up
// the resulting type and bail out.
const cached = cache.get(key);
if (cached) {
return cached;
}
}
const type = getTypeFromFlowType(flowType);
pushIfUnique(antecedentTypes, type);
// If an antecedent type is not a subset of the declared type, we need to perform
// subtype reduction. This happens when a "foreign" type is injected into the control
// flow using the instanceof operator or a user defined type predicate.
if (!isTypeSubsetOf(type, initialType)) {
subtypeReduction = true;
}
// If the type at a particular antecedent path is the declared type there is no
// reason to process more antecedents since the only possible outcome is subtypes
// that will be removed in the final union type anyway.
if (type === declaredType) {
break;
}
}
// The result is incomplete if the first antecedent (the non-looping control flow path)
// is incomplete.
const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal);
if (isIncomplete(firstAntecedentType!)) {
return createFlowType(result, /*incomplete*/ true);
}
cache.set(key, result);
return result;
}
// At flow control branch or loop junctions, if the type along every antecedent code path
// is an evolving array type, we construct a combined evolving array type. Otherwise we
// finalize all evolving array types.
function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) {
if (isEvolvingArrayTypeList(types)) {
return getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType)));
}
const result = recombineUnknownType(getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction));
if (result !== declaredType && result.flags & declaredType.flags & TypeFlags.Union && arrayIsEqualTo((result as UnionType).types, (declaredType as UnionType).types)) {
return declaredType;
}
return result;
}
function getCandidateDiscriminantPropertyAccess(expr: Expression) {
if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference) || isObjectLiteralMethod(reference)) {
// When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in
// getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or
// parameter declared in the same parameter list is a candidate.
if (isIdentifier(expr)) {
const symbol = getResolvedSymbol(expr);
const declaration = getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration;
if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) {
return declaration;
}
}
}
else if (isAccessExpression(expr)) {
// An access expression is a candidate if the reference matches the left hand expression.
if (isMatchingReference(reference, expr.expression)) {
return expr;
}
}
else if (isIdentifier(expr)) {
const symbol = getResolvedSymbol(expr);
if (isConstantVariable(symbol)) {
const declaration = symbol.valueDeclaration!;
// Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
if (
isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) &&
isMatchingReference(reference, declaration.initializer.expression)
) {
return declaration.initializer;
}
// Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
if (isBindingElement(declaration) && !declaration.initializer) {
const parent = declaration.parent.parent;
if (
isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) &&
isMatchingReference(reference, parent.initializer)
) {
return declaration;
}
}
}
}
return undefined;
}
function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
// As long as the computed type is a subset of the declared type, we use the full declared type to detect
// a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type
// predicate narrowing, we use the actual computed type.
if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) {
const access = getCandidateDiscriminantPropertyAccess(expr);
if (access) {
const name = getAccessedPropertyName(access);
if (name) {
const type = declaredType.flags & TypeFlags.Union && isTypeSubsetOf(computedType, declaredType) ? declaredType : computedType;
if (isDiscriminantProperty(type, name)) {
return access;
}
}
}
}
return undefined;
}
function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, narrowType: (t: Type) => Type): Type {
const propName = getAccessedPropertyName(access);
if (propName === undefined) {
return type;
}
const optionalChain = isOptionalChain(access);
const removeNullable = strictNullChecks && (optionalChain || isNonNullAccess(access)) && maybeTypeOfKind(type, TypeFlags.Nullable);
let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName);
if (!propType) {
return type;
}
propType = removeNullable && optionalChain ? getOptionalType(propType) : propType;
const narrowedPropType = narrowType(propType);
return filterType(type, t => {
const discriminantType = getTypeOfPropertyOrIndexSignatureOfType(t, propName) || unknownType;
return !(discriminantType.flags & TypeFlags.Never) && !(narrowedPropType.flags & TypeFlags.Never) && areTypesComparable(narrowedPropType, discriminantType);
});
}
function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, operator: SyntaxKind, value: Expression, assumeTrue: boolean) {
if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) {
const keyPropertyName = getKeyPropertyName(type as UnionType);
if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) {
const candidate = getConstituentTypeForKeyType(type as UnionType, getTypeOfExpression(value));
if (candidate) {
return operator === (assumeTrue ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken) ? candidate :
isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(type, candidate) :
type;
}
}
}
return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue));
}
function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, data: FlowSwitchClauseData) {
if (data.clauseStart < data.clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) {
const clauseTypes = getSwitchClauseTypes(data.switchStatement).slice(data.clauseStart, data.clauseEnd);
const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType));
if (candidate !== unknownType) {
return candidate;
}
}
return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, data));
}
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
if (isMatchingReference(reference, expr)) {
return getAdjustedTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
}
if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
}
const access = getDiscriminantPropertyAccess(expr, type);
if (access) {
return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
}
return type;
}
function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) {
const prop = getPropertyOfType(type, propName);
return prop ?
!!(prop.flags & SymbolFlags.Optional || getCheckFlags(prop) & CheckFlags.Partial) || assumeTrue :
!!getApplicableIndexInfoForName(type, propName) || !assumeTrue;
}
function narrowTypeByInKeyword(type: Type, nameType: StringLiteralType | NumberLiteralType | UniqueESSymbolType, assumeTrue: boolean) {
const name = getPropertyNameFromType(nameType);
const isKnownProperty = someType(type, t => isTypePresencePossible(t, name, /*assumeTrue*/ true));
if (isKnownProperty) {
// If the check is for a known property (i.e. a property declared in some constituent of
// the target type), we filter the target type by presence of absence of the property.
return filterType(type, t => isTypePresencePossible(t, name, assumeTrue));
}
if (assumeTrue) {
// If the check is for an unknown property, we intersect the target type with `Record`,
// where X is the name of the property.
const recordSymbol = getGlobalRecordSymbol();
if (recordSymbol) {
return getIntersectionType([type, getTypeAliasInstantiation(recordSymbol, [nameType, unknownType])]);
}
}
return type;
}
function narrowTypeByBooleanComparison(type: Type, expr: Expression, bool: BooleanLiteral, operator: BinaryOperator, assumeTrue: boolean): Type {
assumeTrue = (assumeTrue !== (bool.kind === SyntaxKind.TrueKeyword)) !== (operator !== SyntaxKind.ExclamationEqualsEqualsToken && operator !== SyntaxKind.ExclamationEqualsToken);
return narrowType(type, expr, assumeTrue);
}
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
switch (expr.operatorToken.kind) {
case SyntaxKind.EqualsToken:
case SyntaxKind.BarBarEqualsToken:
case SyntaxKind.AmpersandAmpersandEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue);
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
const operator = expr.operatorToken.kind;
const left = getReferenceCandidate(expr.left);
const right = getReferenceCandidate(expr.right);
if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) {
return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue);
}
if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) {
return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue);
}
if (isMatchingReference(reference, left)) {
return narrowTypeByEquality(type, operator, right, assumeTrue);
}
if (isMatchingReference(reference, right)) {
return narrowTypeByEquality(type, operator, left, assumeTrue);
}
if (strictNullChecks) {
if (optionalChainContainsReference(left, reference)) {
type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue);
}
else if (optionalChainContainsReference(right, reference)) {
type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue);
}
}
const leftAccess = getDiscriminantPropertyAccess(left, type);
if (leftAccess) {
return narrowTypeByDiscriminantProperty(type, leftAccess, operator, right, assumeTrue);
}
const rightAccess = getDiscriminantPropertyAccess(right, type);
if (rightAccess) {
return narrowTypeByDiscriminantProperty(type, rightAccess, operator, left, assumeTrue);
}
if (isMatchingConstructorReference(left)) {
return narrowTypeByConstructor(type, operator, right, assumeTrue);
}
if (isMatchingConstructorReference(right)) {
return narrowTypeByConstructor(type, operator, left, assumeTrue);
}
if (isBooleanLiteral(right) && !isAccessExpression(left)) {
return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue);
}
if (isBooleanLiteral(left) && !isAccessExpression(right)) {
return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue);
}
break;
case SyntaxKind.InstanceOfKeyword:
return narrowTypeByInstanceof(type, expr as InstanceofExpression, assumeTrue);
case SyntaxKind.InKeyword:
if (isPrivateIdentifier(expr.left)) {
return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue);
}
const target = getReferenceCandidate(expr.right);
if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target)) {
const leftType = getTypeOfExpression(expr.left);
if (isTypeUsableAsPropertyName(leftType) && getAccessedPropertyName(reference) === getPropertyNameFromType(leftType)) {
return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined);
}
}
if (isMatchingReference(reference, target)) {
const leftType = getTypeOfExpression(expr.left);
if (isTypeUsableAsPropertyName(leftType)) {
return narrowTypeByInKeyword(type, leftType, assumeTrue);
}
}
break;
case SyntaxKind.CommaToken:
return narrowType(type, expr.right, assumeTrue);
// Ordinarily we won't see && and || expressions in control flow analysis because the Binder breaks those
// expressions down to individual conditional control flows. However, we may encounter them when analyzing
// aliased conditional expressions.
case SyntaxKind.AmpersandAmpersandToken:
return assumeTrue ?
narrowType(narrowType(type, expr.left, /*assumeTrue*/ true), expr.right, /*assumeTrue*/ true) :
getUnionType([narrowType(type, expr.left, /*assumeTrue*/ false), narrowType(type, expr.right, /*assumeTrue*/ false)]);
case SyntaxKind.BarBarToken:
return assumeTrue ?
getUnionType([narrowType(type, expr.left, /*assumeTrue*/ true), narrowType(type, expr.right, /*assumeTrue*/ true)]) :
narrowType(narrowType(type, expr.left, /*assumeTrue*/ false), expr.right, /*assumeTrue*/ false);
}
return type;
}
function narrowTypeByPrivateIdentifierInInExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
const target = getReferenceCandidate(expr.right);
if (!isMatchingReference(reference, target)) {
return type;
}
Debug.assertNode(expr.left, isPrivateIdentifier);
const symbol = getSymbolForPrivateIdentifierExpression(expr.left);
if (symbol === undefined) {
return type;
}
const classSymbol = symbol.parent!;
const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration"))
? getTypeOfSymbol(classSymbol) as InterfaceType
: getDeclaredTypeOfSymbol(classSymbol);
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
}
function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
// We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows:
// When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch.
// When operator is !== and type of value excludes undefined, null and undefined is removed from type of obj in false branch.
// When operator is == and type of value excludes null and undefined, null and undefined is removed from type of obj in true branch.
// When operator is != and type of value excludes null and undefined, null and undefined is removed from type of obj in false branch.
// When operator is === and type of value is undefined, null and undefined is removed from type of obj in false branch.
// When operator is !== and type of value is undefined, null and undefined is removed from type of obj in true branch.
// When operator is == and type of value is null or undefined, null and undefined is removed from type of obj in false branch.
// When operator is != and type of value is null or undefined, null and undefined is removed from type of obj in true branch.
const equalsOperator = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken;
const nullableFlags = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? TypeFlags.Nullable : TypeFlags.Undefined;
const valueType = getTypeOfExpression(value);
// Note that we include any and unknown in the exclusion test because their domain includes null and undefined.
const removeNullable = equalsOperator !== assumeTrue && everyType(valueType, t => !!(t.flags & nullableFlags)) ||
equalsOperator === assumeTrue && everyType(valueType, t => !(t.flags & (TypeFlags.AnyOrUnknown | nullableFlags)));
return removeNullable ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
}
function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
if (type.flags & TypeFlags.Any) {
return type;
}
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
assumeTrue = !assumeTrue;
}
const valueType = getTypeOfExpression(value);
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
if (valueType.flags & TypeFlags.Nullable) {
if (!strictNullChecks) {
return type;
}
const facts = doubleEquals ?
assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull :
valueType.flags & TypeFlags.Null ?
assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined;
return getAdjustedTypeWithFacts(type, facts);
}
if (assumeTrue) {
if (!doubleEquals && (type.flags & TypeFlags.Unknown || someType(type, isEmptyAnonymousObjectType))) {
if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) || isEmptyAnonymousObjectType(valueType)) {
return valueType;
}
if (valueType.flags & TypeFlags.Object) {
return nonPrimitiveType;
}
}
const filteredType = filterType(type, t => areTypesComparable(t, valueType) || doubleEquals && isCoercibleUnderDoubleEquals(t, valueType));
return replacePrimitivesWithLiterals(filteredType, valueType);
}
if (isUnitType(valueType)) {
return filterType(type, t => !(isUnitLikeType(t) && areTypesComparable(t, valueType)));
}
return type;
}
function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type {
// We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
assumeTrue = !assumeTrue;
}
const target = getReferenceCandidate(typeOfExpr.expression);
if (!isMatchingReference(reference, target)) {
if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) {
type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
}
const propertyAccess = getDiscriminantPropertyAccess(target, type);
if (propertyAccess) {
return narrowTypeByDiscriminant(type, propertyAccess, t => narrowTypeByLiteralExpression(t, literal, assumeTrue));
}
return type;
}
return narrowTypeByLiteralExpression(type, literal, assumeTrue);
}
function narrowTypeByLiteralExpression(type: Type, literal: LiteralExpression, assumeTrue: boolean) {
return assumeTrue ?
narrowTypeByTypeName(type, literal.text) :
getAdjustedTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject);
}
function narrowTypeBySwitchOptionalChainContainment(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData, clauseCheck: (type: Type) => boolean) {
const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck);
return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type;
}
function narrowTypeBySwitchOnDiscriminant(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData) {
// We only narrow if all case expressions specify
// values with unit types, except for the case where
// `type` is unknown. In this instance we map object
// types to the nonPrimitive type and narrow with that.
const switchTypes = getSwitchClauseTypes(switchStatement);
if (!switchTypes.length) {
return type;
}
const clauseTypes = switchTypes.slice(clauseStart, clauseEnd);
const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType);
if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) {
let groundClauseTypes: Type[] | undefined;
for (let i = 0; i < clauseTypes.length; i += 1) {
const t = clauseTypes[i];
if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) {
if (groundClauseTypes !== undefined) {
groundClauseTypes.push(t);
}
}
else if (t.flags & TypeFlags.Object) {
if (groundClauseTypes === undefined) {
groundClauseTypes = clauseTypes.slice(0, i);
}
groundClauseTypes.push(nonPrimitiveType);
}
else {
return type;
}
}
return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes);
}
const discriminantType = getUnionType(clauseTypes);
const caseType = discriminantType.flags & TypeFlags.Never ? neverType :
replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType);
if (!hasDefaultClause) {
return caseType;
}
const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, t.flags & TypeFlags.Undefined ? undefinedType : getRegularTypeOfLiteralType(extractUnitType(t)))));
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
}
function narrowTypeByTypeName(type: Type, typeName: string) {
switch (typeName) {
case "string":
return narrowTypeByTypeFacts(type, stringType, TypeFacts.TypeofEQString);
case "number":
return narrowTypeByTypeFacts(type, numberType, TypeFacts.TypeofEQNumber);
case "bigint":
return narrowTypeByTypeFacts(type, bigintType, TypeFacts.TypeofEQBigInt);
case "boolean":
return narrowTypeByTypeFacts(type, booleanType, TypeFacts.TypeofEQBoolean);
case "symbol":
return narrowTypeByTypeFacts(type, esSymbolType, TypeFacts.TypeofEQSymbol);
case "object":
return type.flags & TypeFlags.Any ? type : getUnionType([narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQObject), narrowTypeByTypeFacts(type, nullType, TypeFacts.EQNull)]);
case "function":
return type.flags & TypeFlags.Any ? type : narrowTypeByTypeFacts(type, globalFunctionType, TypeFacts.TypeofEQFunction);
case "undefined":
return narrowTypeByTypeFacts(type, undefinedType, TypeFacts.EQUndefined);
}
return narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQHostObject);
}
function narrowTypeByTypeFacts(type: Type, impliedType: Type, facts: TypeFacts) {
return mapType(type, t =>
// We first check if a constituent is a subtype of the implied type. If so, we either keep or eliminate
// the constituent based on its type facts. We use the strict subtype relation because it treats `object`
// as a subtype of `{}`, and we need the type facts check because function types are subtypes of `object`,
// but are classified as "function" according to `typeof`.
isTypeRelatedTo(t, impliedType, strictSubtypeRelation) ? hasTypeFacts(t, facts) ? t : neverType :
// We next check if the consituent is a supertype of the implied type. If so, we substitute the implied
// type. This handles top types like `unknown` and `{}`, and supertypes like `{ toString(): string }`.
isTypeSubtypeOf(impliedType, t) ? impliedType :
// Neither the constituent nor the implied type is a subtype of the other, however their domains may still
// overlap. For example, an unconstrained type parameter and type `string`. If the type facts indicate
// possible overlap, we form an intersection. Otherwise, we eliminate the constituent.
hasTypeFacts(t, facts) ? getIntersectionType([t, impliedType]) :
neverType);
}
function narrowTypeBySwitchOnTypeOf(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): Type {
const witnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
if (!witnesses) {
return type;
}
// Equal start and end denotes implicit fallthrough; undefined marks explicit default clause.
const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause);
const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd);
if (hasDefaultClause) {
// In the default clause we filter constituents down to those that are not-equal to all handled cases.
const notEqualFacts = getNotEqualFactsFromTypeofSwitch(clauseStart, clauseEnd, witnesses);
return filterType(type, t => getTypeFacts(t, notEqualFacts) === notEqualFacts);
}
// In the non-default cause we create a union of the type narrowed by each of the listed cases.
const clauseWitnesses = witnesses.slice(clauseStart, clauseEnd);
return getUnionType(map(clauseWitnesses, text => text ? narrowTypeByTypeName(type, text) : neverType));
}
function narrowTypeBySwitchOnTrue(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): Type {
const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause);
const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd);
// First, narrow away all of the cases that preceded this set of cases.
for (let i = 0; i < clauseStart; i++) {
const clause = switchStatement.caseBlock.clauses[i];
if (clause.kind === SyntaxKind.CaseClause) {
type = narrowType(type, clause.expression, /*assumeTrue*/ false);
}
}
// If our current set has a default, then none the other cases were hit either.
// There's no point in narrowing by the the other cases in the set, since we can
// get here through other paths.
if (hasDefaultClause) {
for (let i = clauseEnd; i < switchStatement.caseBlock.clauses.length; i++) {
const clause = switchStatement.caseBlock.clauses[i];
if (clause.kind === SyntaxKind.CaseClause) {
type = narrowType(type, clause.expression, /*assumeTrue*/ false);
}
}
return type;
}
// Now, narrow based on the cases in this set.
const clauses = switchStatement.caseBlock.clauses.slice(clauseStart, clauseEnd);
return getUnionType(map(clauses, clause => clause.kind === SyntaxKind.CaseClause ? narrowType(type, clause.expression, /*assumeTrue*/ true) : neverType));
}
function isMatchingConstructorReference(expr: Expression) {
return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" ||
isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") &&
isMatchingReference(reference, expr.expression);
}
function narrowTypeByConstructor(type: Type, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type {
// Do not narrow when checking inequality.
if (assumeTrue ? (operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) : (operator !== SyntaxKind.ExclamationEqualsToken && operator !== SyntaxKind.ExclamationEqualsEqualsToken)) {
return type;
}
// Get the type of the constructor identifier expression, if it is not a function then do not narrow.
const identifierType = getTypeOfExpression(identifier);
if (!isFunctionType(identifierType) && !isConstructorType(identifierType)) {
return type;
}
// Get the prototype property of the type identifier so we can find out its type.
const prototypeProperty = getPropertyOfType(identifierType, "prototype" as __String);
if (!prototypeProperty) {
return type;
}
// Get the type of the prototype, if it is undefined, or the global `Object` or `Function` types then do not narrow.
const prototypeType = getTypeOfSymbol(prototypeProperty);
const candidate = !isTypeAny(prototypeType) ? prototypeType : undefined;
if (!candidate || candidate === globalObjectType || candidate === globalFunctionType) {
return type;
}
// If the type that is being narrowed is `any` then just return the `candidate` type since every type is a subtype of `any`.
if (isTypeAny(type)) {
return candidate;
}
// Filter out types that are not considered to be "constructed by" the `candidate` type.
return filterType(type, t => isConstructedBy(t, candidate));
function isConstructedBy(source: Type, target: Type) {
// If either the source or target type are a class type then we need to check that they are the same exact type.
// This is because you may have a class `A` that defines some set of properties, and another class `B`
// that defines the same set of properties as class `A`, in that case they are structurally the same
// type, but when you do something like `instanceOfA.constructor === B` it will return false.
if (
source.flags & TypeFlags.Object && getObjectFlags(source) & ObjectFlags.Class ||
target.flags & TypeFlags.Object && getObjectFlags(target) & ObjectFlags.Class
) {
return source.symbol === target.symbol;
}
// For all other types just check that the `source` type is a subtype of the `target` type.
return isTypeSubtypeOf(source, target);
}
}
function narrowTypeByInstanceof(type: Type, expr: InstanceofExpression, assumeTrue: boolean): Type {
const left = getReferenceCandidate(expr.left);
if (!isMatchingReference(reference, left)) {
if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) {
return getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
}
return type;
}
const right = expr.right;
const rightType = getTypeOfExpression(right);
if (!isTypeDerivedFrom(rightType, globalObjectType)) {
return type;
}
// if the right-hand side has an object type with a custom `[Symbol.hasInstance]` method, and that method
// has a type predicate, use the type predicate to perform narrowing. This allows normal `object` types to
// participate in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator.
const signature = getEffectsSignature(expr);
const predicate = signature && getTypePredicateOfSignature(signature);
if (predicate && predicate.kind === TypePredicateKind.Identifier && predicate.parameterIndex === 0) {
return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ true);
}
if (!isTypeDerivedFrom(rightType, globalFunctionType)) {
return type;
}
const instanceType = mapType(rightType, getInstanceType);
// Don't narrow from `any` if the target type is exactly `Object` or `Function`, and narrow
// in the false branch only if the target is a non-empty object type.
if (
isTypeAny(type) && (instanceType === globalObjectType || instanceType === globalFunctionType) ||
!assumeTrue && !(instanceType.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(instanceType))
) {
return type;
}
return getNarrowedType(type, instanceType, assumeTrue, /*checkDerived*/ true);
}
function getInstanceType(constructorType: Type) {
const prototypePropertyType = getTypeOfPropertyOfType(constructorType, "prototype" as __String);
if (prototypePropertyType && !isTypeAny(prototypePropertyType)) {
return prototypePropertyType;
}
const constructSignatures = getSignaturesOfType(constructorType, SignatureKind.Construct);
if (constructSignatures.length) {
return getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature))));
}
// We use the empty object type to indicate we don't know the type of objects created by
// this constructor function.
return emptyObjectType;
}
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean): Type {
const key = type.flags & TypeFlags.Union ? `N${getTypeId(type)},${getTypeId(candidate)},${(assumeTrue ? 1 : 0) | (checkDerived ? 2 : 0)}` : undefined;
return getCachedType(key) ?? setCachedType(key, getNarrowedTypeWorker(type, candidate, assumeTrue, checkDerived));
}
function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) {
if (!assumeTrue) {
if (type === candidate) {
return neverType;
}
if (checkDerived) {
return filterType(type, t => !isTypeDerivedFrom(t, candidate));
}
type = type.flags & TypeFlags.Unknown ? unknownUnionType : type;
const trueType = getNarrowedType(type, candidate, /*assumeTrue*/ true, /*checkDerived*/ false);
return recombineUnknownType(filterType(type, t => !isTypeSubsetOf(t, trueType)));
}
if (type.flags & TypeFlags.AnyOrUnknown) {
return candidate;
}
if (type === candidate) {
return candidate;
}
// We first attempt to filter the current type, narrowing constituents as appropriate and removing
// constituents that are unrelated to the candidate.
const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf;
const keyPropertyName = type.flags & TypeFlags.Union ? getKeyPropertyName(type as UnionType) : undefined;
const narrowedType = mapType(candidate, c => {
// If a discriminant property is available, use that to reduce the type.
const discriminant = keyPropertyName && getTypeOfPropertyOfType(c, keyPropertyName);
const matching = discriminant && getConstituentTypeForKeyType(type as UnionType, discriminant);
// For each constituent t in the current type, if t and and c are directly related, pick the most
// specific of the two. When t and c are related in both directions, we prefer c for type predicates
// because that is the asserted type, but t for `instanceof` because generics aren't reflected in
// prototype object types.
const directlyRelated = mapType(
matching || type,
checkDerived ?
t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType :
t => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType,
);
// If no constituents are directly related, create intersections for any generic constituents that
// are related by constraint.
return directlyRelated.flags & TypeFlags.Never ?
mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) :
directlyRelated;
});
// If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two
// based on assignability, or as a last resort produce an intersection.
return !(narrowedType.flags & TypeFlags.Never) ? narrowedType :
isTypeSubtypeOf(candidate, type) ? candidate :
isTypeAssignableTo(type, candidate) ? type :
isTypeAssignableTo(candidate, type) ? candidate :
getIntersectionType([type, candidate]);
}
function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
if (hasMatchingArgument(callExpression, reference)) {
const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined;
const predicate = signature && getTypePredicateOfSignature(signature);
if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) {
return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue);
}
}
if (containsMissingType(type) && isAccessExpression(reference) && isPropertyAccessExpression(callExpression.expression)) {
const callAccess = callExpression.expression;
if (
isMatchingReference(reference.expression, getReferenceCandidate(callAccess.expression)) &&
isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && callExpression.arguments.length === 1
) {
const argument = callExpression.arguments[0];
if (isStringLiteralLike(argument) && getAccessedPropertyName(reference) === escapeLeadingUnderscores(argument.text)) {
return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined);
}
}
}
return type;
}
function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type {
// Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function'
if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) {
const predicateArgument = getTypePredicateArgument(predicate, callExpression);
if (predicateArgument) {
if (isMatchingReference(reference, predicateArgument)) {
return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false);
}
if (
strictNullChecks && optionalChainContainsReference(predicateArgument, reference) &&
(
assumeTrue && !(hasTypeFacts(predicate.type, TypeFacts.EQUndefined)) ||
!assumeTrue && everyType(predicate.type, isNullableType)
)
) {
type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
}
const access = getDiscriminantPropertyAccess(predicateArgument, type);
if (access) {
return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, /*checkDerived*/ false));
}
}
}
return type;
}
// Narrow the given type based on the given expression having the assumed boolean value. The returned type
// will be a subtype or the same type as the argument.
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
// for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a`
if (
isExpressionOfOptionalChainRoot(expr) ||
isBinaryExpression(expr.parent) && (expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken || expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionEqualsToken) && expr.parent.left === expr
) {
return narrowTypeByOptionality(type, expr, assumeTrue);
}
switch (expr.kind) {
case SyntaxKind.Identifier:
// When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline
// up to five levels of aliased conditional expressions that are themselves declared as const variables.
if (!isMatchingReference(reference, expr) && inlineLevel < 5) {
const symbol = getResolvedSymbol(expr as Identifier);
if (isConstantVariable(symbol)) {
const declaration = symbol.valueDeclaration;
if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isConstantReference(reference)) {
inlineLevel++;
const result = narrowType(type, declaration.initializer, assumeTrue);
inlineLevel--;
return result;
}
}
}
// falls through
case SyntaxKind.ThisKeyword:
case SyntaxKind.SuperKeyword:
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
return narrowTypeByTruthiness(type, expr, assumeTrue);
case SyntaxKind.CallExpression:
return narrowTypeByCallExpression(type, expr as CallExpression, assumeTrue);
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.NonNullExpression:
case SyntaxKind.SatisfiesExpression:
return narrowType(type, (expr as ParenthesizedExpression | NonNullExpression | SatisfiesExpression).expression, assumeTrue);
case SyntaxKind.BinaryExpression:
return narrowTypeByBinaryExpression(type, expr as BinaryExpression, assumeTrue);
case SyntaxKind.PrefixUnaryExpression:
if ((expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) {
return narrowType(type, (expr as PrefixUnaryExpression).operand, !assumeTrue);
}
break;
}
return type;
}
function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type {
if (isMatchingReference(reference, expr)) {
return getAdjustedTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull);
}
const access = getDiscriminantPropertyAccess(expr, type);
if (access) {
return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull));
}
return type;
}
}
function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) {
symbol = getExportSymbolOfValueSymbolIfExported(symbol);
// If we have an identifier or a property access at the given location, if the location is
// an dotted name expression, and if the location is not an assignment target, obtain the type
// of the expression (which will reflect control flow analysis). If the expression indeed
// resolved to the given symbol, return the narrowed type.
if (location.kind === SyntaxKind.Identifier || location.kind === SyntaxKind.PrivateIdentifier) {
if (isRightSideOfQualifiedNameOrPropertyAccess(location)) {
location = location.parent;
}
if (isExpressionNode(location) && (!isAssignmentTarget(location) || isWriteAccess(location))) {
const type = removeOptionalTypeMarker(
isWriteAccess(location) && location.kind === SyntaxKind.PropertyAccessExpression ?
checkPropertyAccessExpression(location as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true) :
getTypeOfExpression(location as Expression),
);
if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) {
return type;
}
}
}
if (isDeclarationName(location) && isSetAccessor(location.parent) && getAnnotatedAccessorTypeNode(location.parent)) {
return getWriteTypeOfAccessors(location.parent.symbol);
}
// The location isn't a reference to the given symbol, meaning we're being asked
// a hypothetical question of what type the symbol would have if there was a reference
// to it at the given location. Since we have no control flow information for the
// hypothetical reference (control flow information is created and attached by the
// binder), we simply return the declared type of the symbol.
return isRightSideOfAccessExpression(location) && isWriteAccess(location.parent) ? getWriteTypeOfSymbol(symbol) : getNonMissingTypeOfSymbol(symbol);
}
function getControlFlowContainer(node: Node): Node {
return findAncestor(node.parent, node =>
isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) ||
node.kind === SyntaxKind.ModuleBlock ||
node.kind === SyntaxKind.SourceFile ||
node.kind === SyntaxKind.PropertyDeclaration)!;
}
// Check if a parameter, catch variable, or mutable local variable is assigned anywhere definitely
function isSymbolAssignedDefinitely(symbol: Symbol) {
if (symbol.lastAssignmentPos !== undefined) {
return symbol.lastAssignmentPos < 0;
}
return isSymbolAssigned(symbol) && symbol.lastAssignmentPos !== undefined && symbol.lastAssignmentPos < 0;
}
// Check if a parameter, catch variable, or mutable local variable is assigned anywhere
function isSymbolAssigned(symbol: Symbol) {
return !isPastLastAssignment(symbol, /*location*/ undefined);
}
// Return true if there are no assignments to the given symbol or if the given location
// is past the last assignment to the symbol.
function isPastLastAssignment(symbol: Symbol, location: Node | undefined) {
const parent = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile);
if (!parent) {
return false;
}
const links = getNodeLinks(parent);
if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) {
links.flags |= NodeCheckFlags.AssignmentsMarked;
if (!hasParentWithAssignmentsMarked(parent)) {
markNodeAssignments(parent);
}
}
return !symbol.lastAssignmentPos || location && Math.abs(symbol.lastAssignmentPos) < location.pos;
}
// Check if a parameter or catch variable (or their bindings elements) is assigned anywhere
function isSomeSymbolAssigned(rootDeclaration: Node) {
Debug.assert(isVariableDeclaration(rootDeclaration) || isParameter(rootDeclaration));
return isSomeSymbolAssignedWorker(rootDeclaration.name);
}
function isSomeSymbolAssignedWorker(node: BindingName): boolean {
if (node.kind === SyntaxKind.Identifier) {
return isSymbolAssigned(getSymbolOfDeclaration(node.parent as Declaration));
}
return some(node.elements, e => e.kind !== SyntaxKind.OmittedExpression && isSomeSymbolAssignedWorker(e.name));
}
function hasParentWithAssignmentsMarked(node: Node) {
return !!findAncestor(node.parent, node => isFunctionOrSourceFile(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked));
}
function isFunctionOrSourceFile(node: Node) {
return isFunctionLikeDeclaration(node) || isSourceFile(node);
}
// For all assignments within the given root node, record the last assignment source position for all
// referenced parameters and mutable local variables. When assignments occur in nested functions or
// references occur in export specifiers, record Number.MAX_VALUE as the assignment position. When
// assignments occur in compound statements, record the ending source position of the compound statement
// as the assignment position (this is more conservative than full control flow analysis, but requires
// only a single walk over the AST).
function markNodeAssignments(node: Node) {
switch (node.kind) {
case SyntaxKind.Identifier:
const assigmentTarget = getAssignmentTargetKind(node);
if (assigmentTarget !== AssignmentKind.None) {
const symbol = getResolvedSymbol(node as Identifier);
const hasDefiniteAssignment = assigmentTarget === AssignmentKind.Definite || (symbol.lastAssignmentPos !== undefined && symbol.lastAssignmentPos < 0);
if (isParameterOrMutableLocalVariable(symbol)) {
if (symbol.lastAssignmentPos === undefined || Math.abs(symbol.lastAssignmentPos) !== Number.MAX_VALUE) {
const referencingFunction = findAncestor(node, isFunctionOrSourceFile);
const declaringFunction = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile);
symbol.lastAssignmentPos = referencingFunction === declaringFunction ? extendAssignmentPosition(node, symbol.valueDeclaration!) : Number.MAX_VALUE;
}
if (hasDefiniteAssignment && symbol.lastAssignmentPos > 0) {
symbol.lastAssignmentPos *= -1;
}
}
}
return;
case SyntaxKind.ExportSpecifier:
const exportDeclaration = (node as ExportSpecifier).parent.parent;
const name = (node as ExportSpecifier).propertyName || (node as ExportSpecifier).name;
if (!(node as ExportSpecifier).isTypeOnly && !exportDeclaration.isTypeOnly && !exportDeclaration.moduleSpecifier && name.kind !== SyntaxKind.StringLiteral) {
const symbol = resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true);
if (symbol && isParameterOrMutableLocalVariable(symbol)) {
const sign = symbol.lastAssignmentPos !== undefined && symbol.lastAssignmentPos < 0 ? -1 : 1;
symbol.lastAssignmentPos = sign * Number.MAX_VALUE;
}
}
return;
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.EnumDeclaration:
return;
}
if (isTypeNode(node)) {
return;
}
forEachChild(node, markNodeAssignments);
}
// Extend the position of the given assignment target node to the end of any intervening variable statement,
// expression statement, compound statement, or class declaration occurring between the node and the given
// declaration node.
function extendAssignmentPosition(node: Node, declaration: Declaration) {
let pos = node.pos;
while (node && node.pos > declaration.pos) {
switch (node.kind) {
case SyntaxKind.VariableStatement:
case SyntaxKind.ExpressionStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.WithStatement:
case SyntaxKind.SwitchStatement:
case SyntaxKind.TryStatement:
case SyntaxKind.ClassDeclaration:
pos = node.end;
}
node = node.parent;
}
return pos;
}
function isConstantVariable(symbol: Symbol) {
return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Constant) !== 0;
}
function isParameterOrMutableLocalVariable(symbol: Symbol) {
// Return true if symbol is a parameter, a catch clause variable, or a mutable local variable
const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration);
return !!declaration && (
isParameter(declaration) ||
isVariableDeclaration(declaration) && (isCatchClause(declaration.parent) || isMutableLocalVariableDeclaration(declaration))
);
}
function isMutableLocalVariableDeclaration(declaration: VariableDeclaration) {
// Return true if symbol is a non-exported and non-global `let` variable
return !!(declaration.parent.flags & NodeFlags.Let) && !(
getCombinedModifierFlags(declaration) & ModifierFlags.Export ||
declaration.parent.parent.kind === SyntaxKind.VariableStatement && isGlobalSourceFile(declaration.parent.parent.parent)
);
}
function parameterInitializerContainsUndefined(declaration: ParameterDeclaration): boolean {
const links = getNodeLinks(declaration);
if (links.parameterInitializerContainsUndefined === undefined) {
if (!pushTypeResolution(declaration, TypeSystemPropertyName.ParameterInitializerContainsUndefined)) {
reportCircularityError(declaration.symbol);
return true;
}
const containsUndefined = !!(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined));
if (!popTypeResolution()) {
reportCircularityError(declaration.symbol);
return true;
}
links.parameterInitializerContainsUndefined ??= containsUndefined;
}
return links.parameterInitializerContainsUndefined;
}
/** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */
function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type {
const removeUndefined = strictNullChecks &&
declaration.kind === SyntaxKind.Parameter &&
declaration.initializer &&
hasTypeFacts(declaredType, TypeFacts.IsUndefined) &&
!parameterInitializerContainsUndefined(declaration);
return removeUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType;
}
function isConstraintPosition(type: Type, node: Node) {
const parent = node.parent;
// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of
// a generic type without a nullable constraint and x is a generic type. This is because when both obj
// and x are of generic types T and K, we want the resulting type to be T[K].
return parent.kind === SyntaxKind.PropertyAccessExpression ||
parent.kind === SyntaxKind.QualifiedName ||
parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node ||
parent.kind === SyntaxKind.NewExpression && (parent as NewExpression).expression === node ||
parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node &&
!(someType(type, isGenericTypeWithoutNullableConstraint) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression)));
}
function isGenericTypeWithUnionConstraint(type: Type): boolean {
return type.flags & TypeFlags.Intersection ?
some((type as IntersectionType).types, isGenericTypeWithUnionConstraint) :
!!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union));
}
function isGenericTypeWithoutNullableConstraint(type: Type): boolean {
return type.flags & TypeFlags.Intersection ?
some((type as IntersectionType).types, isGenericTypeWithoutNullableConstraint) :
!!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable));
}
function hasContextualTypeWithNoGenericTypes(node: Node, checkMode: CheckMode | undefined) {
// Computing the contextual type for a child of a JSX element involves resolving the type of the
// element's tag name, so we exclude that here to avoid circularities.
// If check mode has `CheckMode.RestBindingElement`, we skip binding pattern contextual types,
// as we want the type of a rest element to be generic when possible.
const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) &&
!((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) &&
(checkMode && checkMode & CheckMode.RestBindingElement ?
getContextualType(node, ContextFlags.SkipBindingPatterns)
: getContextualType(node, /*contextFlags*/ undefined));
return contextualType && !isGenericType(contextualType);
}
function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) {
if (isNoInferType(type)) {
type = (type as SubstitutionType).baseType;
}
// When the type of a reference is or contains an instantiable type with a union type constraint, and
// when the reference is in a constraint position (where it is known we'll obtain the apparent type) or
// has a contextual type containing no top-level instantiables (meaning constraints will determine
// assignability), we substitute constraints for all instantiables in the type of the reference to give
// control flow analysis an opportunity to narrow it further. For example, for a reference of a type
// parameter type 'T extends string | undefined' with a contextual type 'string', we substitute
// 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'.
const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) &&
someType(type, isGenericTypeWithUnionConstraint) &&
(isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode));
return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type;
}
function isExportOrExportExpression(location: Node) {
return !!findAncestor(location, n => {
const parent = n.parent;
if (parent === undefined) {
return "quit";
}
if (isExportAssignment(parent)) {
return parent.expression === n && isEntityNameExpression(n);
}
if (isExportSpecifier(parent)) {
return parent.name === n || parent.propertyName === n;
}
return false;
});
}
/**
* This function marks all the imports the given location refers to as `.referenced` in `NodeLinks` (transitively through local import aliases).
* (This corresponds to not getting elided in JS emit.)
* It can be called on *most* nodes in the AST with `ReferenceHint.Unspecified` and will filter its inputs, but care should be taken to avoid calling it on the RHS of an `import =` or specifiers in a `import {} from "..."`,
* unless you *really* want to *definitely* mark those as referenced.
* These shouldn't be directly marked, and should only get marked transitively by the internals of this function.
*
* @param location The location to mark js import refernces for
* @param hint The kind of reference `location` has already been checked to be
* @param propSymbol The optional symbol of the property we're looking up - this is used for property accesses when `const enum`s do not count as references (no `isolatedModules`, no `preserveConstEnums` + export). It will be calculated if not provided.
* @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided (but is costly).
*/
function markLinkedReferences(location: PropertyAccessExpression | QualifiedName, hint: ReferenceHint.Property, propSymbol: Symbol | undefined, parentType: Type): void;
function markLinkedReferences(location: Identifier, hint: ReferenceHint.Identifier): void;
function markLinkedReferences(location: ExportAssignment, hint: ReferenceHint.ExportAssignment): void;
function markLinkedReferences(location: JsxOpeningLikeElement | JsxOpeningFragment, hint: ReferenceHint.Jsx): void;
function markLinkedReferences(location: FunctionLikeDeclaration | MethodSignature, hint: ReferenceHint.AsyncFunction): void;
function markLinkedReferences(location: ImportEqualsDeclaration, hint: ReferenceHint.ExportImportEquals): void;
function markLinkedReferences(location: ExportSpecifier, hint: ReferenceHint.ExportSpecifier): void;
function markLinkedReferences(location: HasDecorators, hint: ReferenceHint.Decorator): void;
function markLinkedReferences(location: Node, hint: ReferenceHint.Unspecified, propSymbol?: Symbol, parentType?: Type): void;
function markLinkedReferences(location: Node, hint: ReferenceHint, propSymbol?: Symbol, parentType?: Type) {
if (!canCollectSymbolAliasAccessabilityData) {
return;
}
if (location.flags & NodeFlags.Ambient && !isPropertySignature(location) && !isPropertyDeclaration(location)) {
// References within types and declaration files are never going to contribute to retaining a JS import,
// except for properties (which can be decorated).
return;
}
switch (hint) {
case ReferenceHint.Identifier:
return markIdentifierAliasReferenced(location as Identifier);
case ReferenceHint.Property:
return markPropertyAliasReferenced(location as PropertyAccessExpression | QualifiedName, propSymbol, parentType);
case ReferenceHint.ExportAssignment:
return markExportAssignmentAliasReferenced(location as ExportAssignment);
case ReferenceHint.Jsx:
return markJsxAliasReferenced(location as JsxOpeningLikeElement | JsxOpeningFragment);
case ReferenceHint.AsyncFunction:
return markAsyncFunctionAliasReferenced(location as FunctionLikeDeclaration | MethodSignature);
case ReferenceHint.ExportImportEquals:
return markImportEqualsAliasReferenced(location as ImportEqualsDeclaration);
case ReferenceHint.ExportSpecifier:
return markExportSpecifierAliasReferenced(location as ExportSpecifier);
case ReferenceHint.Decorator:
return markDecoratorAliasReferenced(location as HasDecorators);
case ReferenceHint.Unspecified: {
// Identifiers in expression contexts are emitted, so we need to follow their referenced aliases and mark them as used
// Some non-expression identifiers are also treated as expression identifiers for this purpose, eg, `a` in `b = {a}` or `q` in `import r = q`
// This is the exception, rather than the rule - most non-expression identifiers are declaration names.
if (isIdentifier(location) && (isExpressionNode(location) || isShorthandPropertyAssignment(location.parent) || (isImportEqualsDeclaration(location.parent) && location.parent.moduleReference === location)) && shouldMarkIdentifierAliasReferenced(location)) {
if (isPropertyAccessOrQualifiedName(location.parent)) {
const left = isPropertyAccessExpression(location.parent) ? location.parent.expression : location.parent.left;
if (left !== location) return; // Only mark the LHS (the RHS is a property lookup)
}
markIdentifierAliasReferenced(location);
return;
}
if (isPropertyAccessOrQualifiedName(location)) {
let topProp: Node | undefined = location;
while (isPropertyAccessOrQualifiedName(topProp)) {
if (isPartOfTypeNode(topProp)) return;
topProp = topProp.parent;
}
return markPropertyAliasReferenced(location);
}
if (isExportAssignment(location)) {
return markExportAssignmentAliasReferenced(location);
}
if (isJsxOpeningLikeElement(location) || isJsxOpeningFragment(location)) {
return markJsxAliasReferenced(location);
}
if (isImportEqualsDeclaration(location)) {
if (isInternalModuleImportEqualsDeclaration(location) || checkExternalImportOrExportDeclaration(location)) {
return markImportEqualsAliasReferenced(location);
}
return;
}
if (isExportSpecifier(location)) {
return markExportSpecifierAliasReferenced(location);
}
if (isFunctionLikeDeclaration(location) || isMethodSignature(location)) {
markAsyncFunctionAliasReferenced(location);
// Might be decorated, fall through to decorator final case
}
if (!compilerOptions.emitDecoratorMetadata) {
return;
}
if (!canHaveDecorators(location) || !hasDecorators(location) || !location.modifiers || !nodeCanBeDecorated(legacyDecorators, location, location.parent, location.parent.parent)) {
return;
}
return markDecoratorAliasReferenced(location);
}
default:
Debug.assertNever(hint, `Unhandled reference hint: ${hint}`);
}
}
function markIdentifierAliasReferenced(location: Identifier) {
const symbol = getResolvedSymbol(location);
if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) {
markAliasReferenced(symbol, location);
}
}
function markPropertyAliasReferenced(location: PropertyAccessExpression | QualifiedName, propSymbol?: Symbol, parentType?: Type) {
const left = isPropertyAccessExpression(location) ? location.expression : location.left;
if (isThisIdentifier(left) || !isIdentifier(left)) {
return;
}
const parentSymbol = getResolvedSymbol(left);
if (!parentSymbol || parentSymbol === unknownSymbol) {
return;
}
// In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums.
// `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined
// here even if `Foo` is not a const enum.
//
// The exceptions are:
// 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and
// 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`.
//
// The property lookup is deferred as much as possible, in as many situations as possible, to avoid alias marking
// pulling on types/symbols it doesn't strictly need to.
if (getIsolatedModules(compilerOptions) || (shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location))) {
markAliasReferenced(parentSymbol, location);
return;
}
// Hereafter, this relies on type checking - but every check prior to this only used symbol information
const leftType = parentType || checkExpressionCached(left);
if (isTypeAny(leftType) || leftType === silentNeverType) {
markAliasReferenced(parentSymbol, location);
return;
}
let prop = propSymbol;
if (!prop && !parentType) {
const right = isPropertyAccessExpression(location) ? location.name : location.right;
const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right);
const assignmentKind = getAssignmentTargetKind(location);
const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType);
prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText);
}
if (
!(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && location.parent.kind === SyntaxKind.EnumMember))
) {
markAliasReferenced(parentSymbol, location);
}
return;
}
function markExportAssignmentAliasReferenced(location: ExportAssignment) {
if (isIdentifier(location.expression)) {
const id = location.expression;
const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location));
if (sym) {
markAliasReferenced(sym, id);
}
}
}
function markJsxAliasReferenced(node: JsxOpeningLikeElement | JsxOpeningFragment) {
if (!getJsxNamespaceContainerForImplicitImport(node)) {
// The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import.
// And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error.
const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.This_JSX_tag_requires_0_to_be_in_scope_but_it_could_not_be_found : undefined;
const jsxFactoryNamespace = getJsxNamespace(node);
const jsxFactoryLocation = isJsxOpeningLikeElement(node) ? node.tagName : node;
const shouldFactoryRefErr = compilerOptions.jsx !== JsxEmit.Preserve && compilerOptions.jsx !== JsxEmit.ReactNative;
// #38720/60122, allow null as jsxFragmentFactory
let jsxFactorySym: Symbol | undefined;
if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) {
jsxFactorySym = resolveName(
jsxFactoryLocation,
jsxFactoryNamespace,
shouldFactoryRefErr ? SymbolFlags.Value : SymbolFlags.Value & ~SymbolFlags.Enum,
jsxFactoryRefErr,
/*isUse*/ true,
);
}
if (jsxFactorySym) {
// Mark local symbol as referenced here because it might not have been marked
// if jsx emit was not jsxFactory as there wont be error being emitted
jsxFactorySym.isReferenced = SymbolFlags.All;
// If react/jsxFactory symbol is alias, mark it as refereced
if (canCollectSymbolAliasAccessabilityData && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) {
markAliasSymbolAsReferenced(jsxFactorySym);
}
}
// if JsxFragment, additionally mark jsx pragma as referenced, since `getJsxNamespace` above would have resolved to only the fragment factory if they are distinct
if (isJsxOpeningFragment(node)) {
const file = getSourceFileOfNode(node);
const entity = getJsxFactoryEntity(file);
if (entity) {
const localJsxNamespace = getFirstIdentifier(entity).escapedText;
resolveName(
jsxFactoryLocation,
localJsxNamespace,
shouldFactoryRefErr ? SymbolFlags.Value : SymbolFlags.Value & ~SymbolFlags.Enum,
jsxFactoryRefErr,
/*isUse*/ true,
);
}
}
}
return;
}
function markAsyncFunctionAliasReferenced(location: FunctionLikeDeclaration | MethodSignature) {
if (languageVersion < ScriptTarget.ES2015) {
if (getFunctionFlags(location) & FunctionFlags.Async) {
const returnTypeNode = getEffectiveReturnTypeNode(location);
markTypeNodeAsReferenced(returnTypeNode);
}
}
}
function markImportEqualsAliasReferenced(location: ImportEqualsDeclaration) {
if (hasSyntacticModifier(location, ModifierFlags.Export)) {
markExportAsReferenced(location);
}
}
function markExportSpecifierAliasReferenced(location: ExportSpecifier) {
if (!location.parent.parent.moduleSpecifier && !location.isTypeOnly && !location.parent.parent.isTypeOnly) {
const exportedName = location.propertyName || location.name;
if (exportedName.kind === SyntaxKind.StringLiteral) {
return; // Skip for invalid syntax like this: export { "x" }
}
const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*isUse*/ true);
if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) {
// Do nothing, non-local symbol
}
else {
const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol);
if (!target || getSymbolFlags(target) & SymbolFlags.Value) {
markExportAsReferenced(location); // marks export as used
markIdentifierAliasReferenced(exportedName); // marks target of export as used
}
}
return;
}
}
function markDecoratorAliasReferenced(node: HasDecorators) {
if (compilerOptions.emitDecoratorMetadata) {
const firstDecorator = find(node.modifiers, isDecorator);
if (!firstDecorator) {
return;
}
checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata);
// we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator.
switch (node.kind) {
case SyntaxKind.ClassDeclaration:
const constructor = getFirstConstructorWithBody(node);
if (constructor) {
for (const parameter of constructor.parameters) {
markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter));
}
}
break;
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor;
const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(node), otherKind);
markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor));
break;
case SyntaxKind.MethodDeclaration:
for (const parameter of node.parameters) {
markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter));
}
markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node));
break;
case SyntaxKind.PropertyDeclaration:
markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node));
break;
case SyntaxKind.Parameter:
markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node));
const containingSignature = node.parent;
for (const parameter of containingSignature.parameters) {
markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter));
}
markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(containingSignature));
break;
}
}
}
function markAliasReferenced(symbol: Symbol, location: Node) {
if (!canCollectSymbolAliasAccessabilityData) {
return;
}
if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) {
const target = resolveAlias(symbol);
if (getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & (SymbolFlags.Value | SymbolFlags.ExportValue)) {
// An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled
// (because the const enum value will not be inlined), or if (2) the alias is an export
// of a const enum declaration that will be preserved.
if (
getIsolatedModules(compilerOptions) ||
shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) ||
!isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target))
) {
markAliasSymbolAsReferenced(symbol);
}
}
}
}
// When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until
// we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of
// the alias as an expression (which recursively takes us back here if the target references another alias).
function markAliasSymbolAsReferenced(symbol: Symbol) {
Debug.assert(canCollectSymbolAliasAccessabilityData);
const links = getSymbolLinks(symbol);
if (!links.referenced) {
links.referenced = true;
const node = getDeclarationOfAliasSymbol(symbol);
if (!node) return Debug.fail();
// We defer checking of the reference of an `import =` until the import itself is referenced,
// This way a chain of imports can be elided if ultimately the final input is only used in a type
// position.
if (isInternalModuleImportEqualsDeclaration(node)) {
if (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) {
// import foo =
const left = getFirstIdentifier(node.moduleReference as EntityNameExpression);
markIdentifierAliasReferenced(left);
}
}
}
}
function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) {
const symbol = getSymbolOfDeclaration(node);
const target = resolveAlias(symbol);
if (target) {
const markAlias = target === unknownSymbol ||
((getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target));
if (markAlias) {
markAliasSymbolAsReferenced(symbol);
}
}
}
function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) {
if (!typeName) return;
const rootName = getFirstIdentifier(typeName);
const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias;
const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ true);
if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) {
if (
canCollectSymbolAliasAccessabilityData
&& symbolIsValue(rootSymbol)
&& !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))
&& !getTypeOnlyAliasDeclaration(rootSymbol)
) {
markAliasSymbolAsReferenced(rootSymbol);
}
else if (
forDecoratorMetadata
&& getIsolatedModules(compilerOptions)
&& getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015
&& !symbolIsValue(rootSymbol)
&& !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration)
) {
const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled);
const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration);
if (aliasDeclaration) {
addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName)));
}
}
}
}
/**
* If a TypeNode can be resolved to a value symbol imported from an external module, it is
* marked as referenced to prevent import elision.
*/
function markTypeNodeAsReferenced(node: TypeNode | undefined) {
markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false);
}
/**
* This function marks the type used for metadata decorator as referenced if it is import
* from external module.
* This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in
* union and intersection type
* @param node
*/
function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void {
const entityName = getEntityNameForDecoratorMetadata(node);
if (entityName && isEntityName(entityName)) {
markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true);
}
}
function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) {
const type = getTypeOfSymbol(symbol);
const declaration = symbol.valueDeclaration;
if (declaration) {
// If we have a non-rest binding element with no initializer declared as a const variable or a const-like
// parameter (a parameter for which there are no assignments in the function body), and if the parent type
// for the destructuring is a union type, one or more of the binding elements may represent discriminant
// properties, and we want the effects of conditional checks on such discriminants to affect the types of
// other binding elements from the same destructuring. Consider:
//
// type Action =
// | { kind: 'A', payload: number }
// | { kind: 'B', payload: string };
//
// function f({ kind, payload }: Action) {
// if (kind === 'A') {
// payload.toFixed();
// }
// if (kind === 'B') {
// payload.toUpperCase();
// }
// }
//
// Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use
// the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference
// as if it occurred in the specified location. We then recompute the narrowed binding element type by
// destructuring from the narrowed parent type.
if (isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) {
const parent = declaration.parent.parent;
const rootDeclaration = getRootDeclaration(parent);
if (rootDeclaration.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(rootDeclaration) & NodeFlags.Constant || rootDeclaration.kind === SyntaxKind.Parameter) {
const links = getNodeLinks(parent);
if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) {
links.flags |= NodeCheckFlags.InCheckIdentifier;
const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal);
const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType);
links.flags &= ~NodeCheckFlags.InCheckIdentifier;
if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) {
const pattern = declaration.parent;
const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode);
if (narrowedType.flags & TypeFlags.Never) {
return neverType;
}
// Destructurings are validated against the parent type elsewhere. Here we disable tuple bounds
// checks because the narrowed type may have lower arity than the full parent type. For example,
// for the declaration [x, y]: [1, 2] | [3], we may have narrowed the parent type to just [3].
return getBindingElementTypeFromParentType(declaration, narrowedType, /*noTupleBoundsCheck*/ true);
}
}
}
}
// If we have a const-like parameter with no type annotation or initializer, and if the parameter is contextually
// typed by a signature with a single rest parameter of a union of tuple types, one or more of the parameters may
// represent discriminant tuple elements, and we want the effects of conditional checks on such discriminants to
// affect the types of other parameters in the same parameter list. Consider:
//
// type Action = [kind: 'A', payload: number] | [kind: 'B', payload: string];
//
// const f: (...args: Action) => void = (kind, payload) => {
// if (kind === 'A') {
// payload.toFixed();
// }
// if (kind === 'B') {
// payload.toUpperCase();
// }
// }
//
// Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use
// the arrow function AST node for '(kind, payload) => ...' as a pseudo-reference and narrow this reference as
// if it occurred in the specified location. We then recompute the narrowed parameter type by indexing into the
// narrowed tuple type.
if (isParameter(declaration) && !declaration.type && !declaration.initializer && !declaration.dotDotDotToken) {
const func = declaration.parent;
if (func.parameters.length >= 2 && isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature && contextualSignature.parameters.length === 1 && signatureHasRestParameter(contextualSignature)) {
const restType = getReducedApparentType(instantiateType(getTypeOfSymbol(contextualSignature.parameters[0]), getInferenceContext(func)?.nonFixingMapper));
if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !some(func.parameters, isSomeSymbolAssigned)) {
const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, location.flowNode);
const index = func.parameters.indexOf(declaration) - (getThisParameter(func) ? 1 : 0);
return getIndexedAccessType(narrowedType, getNumberLiteralType(index));
}
}
}
}
}
return type;
}
/**
* This part of `checkIdentifier` is kept seperate from the rest, so `NodeCheckFlags` (and related diagnostics) can be lazily calculated
* without calculating the flow type of the identifier.
*/
function checkIdentifierCalculateNodeCheckFlags(node: Identifier, symbol: Symbol) {
if (isThisInTypeQuery(node)) return;
// As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects.
// Although in down-level emit of arrow function, we emit it using function expression which means that
// arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects
// will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior.
// To avoid that we will give an error to users if they use arguments objects in arrow function so that they
// can explicitly bound arguments objects
if (symbol === argumentsSymbol) {
if (isInPropertyInitializerOrClassStaticBlock(node, /*ignoreArrowFunctions*/ true)) {
error(node, Diagnostics.arguments_cannot_be_referenced_in_property_initializers_or_class_static_initialization_blocks);
return;
}
let container = getContainingFunction(node);
if (container) {
if (languageVersion < ScriptTarget.ES2015) {
if (container.kind === SyntaxKind.ArrowFunction) {
error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES5_Consider_using_a_standard_function_expression);
}
else if (hasSyntacticModifier(container, ModifierFlags.Async)) {
error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES5_Consider_using_a_standard_function_or_method);
}
}
getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments;
while (container && isArrowFunction(container)) {
container = getContainingFunction(container);
if (container) {
getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments;
}
}
}
return;
}
const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
const targetSymbol = resolveAliasWithDeprecationCheck(localOrExportSymbol, node);
if (isDeprecatedSymbol(targetSymbol) && isUncalledFunctionReference(node, targetSymbol) && targetSymbol.declarations) {
addDeprecatedSuggestion(node, targetSymbol.declarations, node.escapedText as string);
}
const declaration = localOrExportSymbol.valueDeclaration;
if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) {
// When we downlevel classes we may emit some code outside of the class body. Due to the fact the
// class name is double-bound, we must ensure we mark references to the class name so that we can
// emit an alias to the class later.
if (isClassLike(declaration) && declaration.name !== node) {
let container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
while (container.kind !== SyntaxKind.SourceFile && container.parent !== declaration) {
container = getThisContainer(container, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
}
if (container.kind !== SyntaxKind.SourceFile) {
getNodeLinks(declaration).flags |= NodeCheckFlags.ContainsConstructorReference;
getNodeLinks(container).flags |= NodeCheckFlags.ContainsConstructorReference;
getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReference;
}
}
}
checkNestedBlockScopedBinding(node, symbol);
}
function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type {
if (isThisInTypeQuery(node)) {
return checkThisExpression(node);
}
const symbol = getResolvedSymbol(node);
if (symbol === unknownSymbol) {
return errorType;
}
checkIdentifierCalculateNodeCheckFlags(node, symbol);
if (symbol === argumentsSymbol) {
if (isInPropertyInitializerOrClassStaticBlock(node)) {
return errorType;
}
return getTypeOfSymbol(symbol);
}
if (shouldMarkIdentifierAliasReferenced(node)) {
markLinkedReferences(node, ReferenceHint.Identifier);
}
const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
let declaration = localOrExportSymbol.valueDeclaration;
const immediateDeclaration = declaration;
// If the identifier is declared in a binding pattern for which we're currently computing the implied type and the
// reference occurs with the same binding pattern, return the non-inferrable any type. This for example occurs in
// 'const [a, b = a + 1] = [2]' when we're computing the contextual type for the array literal '[2]'.
if (declaration && declaration.kind === SyntaxKind.BindingElement && contains(contextualBindingPatterns, declaration.parent) && findAncestor(node, parent => parent === declaration!.parent)) {
return nonInferrableAnyType;
}
let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node);
const assignmentKind = getAssignmentTargetKind(node);
if (assignmentKind) {
if (
!(localOrExportSymbol.flags & SymbolFlags.Variable) &&
!(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)
) {
const assignmentError = localOrExportSymbol.flags & SymbolFlags.Enum ? Diagnostics.Cannot_assign_to_0_because_it_is_an_enum
: localOrExportSymbol.flags & SymbolFlags.Class ? Diagnostics.Cannot_assign_to_0_because_it_is_a_class
: localOrExportSymbol.flags & SymbolFlags.Module ? Diagnostics.Cannot_assign_to_0_because_it_is_a_namespace
: localOrExportSymbol.flags & SymbolFlags.Function ? Diagnostics.Cannot_assign_to_0_because_it_is_a_function
: localOrExportSymbol.flags & SymbolFlags.Alias ? Diagnostics.Cannot_assign_to_0_because_it_is_an_import
: Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable;
error(node, assignmentError, symbolToString(symbol));
return errorType;
}
if (isReadonlySymbol(localOrExportSymbol)) {
if (localOrExportSymbol.flags & SymbolFlags.Variable) {
error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol));
}
else {
error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol));
}
return errorType;
}
}
const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias;
// We only narrow variables and parameters occurring in a non-assignment position. For all other
// entities we simply return the declared type.
if (localOrExportSymbol.flags & SymbolFlags.Variable) {
if (assignmentKind === AssignmentKind.Definite) {
return isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type;
}
}
else if (isAlias) {
declaration = getDeclarationOfAliasSymbol(symbol);
}
else {
return type;
}
if (!declaration) {
return type;
}
type = getNarrowableTypeForReference(type, node, checkMode);
// The declaration container is the innermost function that encloses the declaration of the variable
// or parameter. The flow container is the innermost function starting with which we analyze the control
// flow graph to determine the control flow based type.
const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter;
const declarationContainer = getControlFlowContainer(declaration);
let flowContainer = getControlFlowContainer(node);
const isOuterVariable = flowContainer !== declarationContainer;
const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent);
const isModuleExports = symbol.flags & SymbolFlags.ModuleExports;
const typeIsAutomatic = type === autoType || type === autoArrayType;
const isAutomaticTypeInNonNull = typeIsAutomatic && node.parent.kind === SyntaxKind.NonNullExpression;
// When the control flow originates in a function expression, arrow function, method, or accessor, and
// we are referencing a closed-over const variable or parameter or mutable local variable past its last
// assignment, we extend the origin of the control flow analysis to include the immediately enclosing
// control flow container.
while (
flowContainer !== declarationContainer && (
flowContainer.kind === SyntaxKind.FunctionExpression ||
flowContainer.kind === SyntaxKind.ArrowFunction ||
isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)
) && (
isConstantVariable(localOrExportSymbol) && type !== autoArrayType ||
isParameterOrMutableLocalVariable(localOrExportSymbol) && isPastLastAssignment(localOrExportSymbol, node)
)
) {
flowContainer = getControlFlowContainer(flowContainer);
}
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
// declaration container are the same).
const isNeverInitialized = immediateDeclaration && isVariableDeclaration(immediateDeclaration) && !immediateDeclaration.initializer && !immediateDeclaration.exclamationToken && isMutableLocalVariableDeclaration(immediateDeclaration) && !isSymbolAssignedDefinitely(symbol);
const assumeInitialized = isParameter || isAlias ||
(isOuterVariable && !isNeverInitialized) ||
isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) ||
type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 ||
isInTypeQuery(node) || isInAmbientOrTypeNode(node) || node.parent.kind === SyntaxKind.ExportSpecifier) ||
node.parent.kind === SyntaxKind.NonNullExpression ||
declaration.kind === SyntaxKind.VariableDeclaration && (declaration as VariableDeclaration).exclamationToken ||
declaration.flags & NodeFlags.Ambient;
const initialType = isAutomaticTypeInNonNull ? undefinedType :
assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) :
typeIsAutomatic ? undefinedType : getOptionalType(type);
const flowType = isAutomaticTypeInNonNull ? getNonNullableType(getFlowTypeOfReference(node, type, initialType, flowContainer)) :
getFlowTypeOfReference(node, type, initialType, flowContainer);
// A variable is considered uninitialized when it is possible to analyze the entire control flow graph
// from declaration to use, and when the variable's declared type doesn't include undefined but the
// control flow based type does include undefined.
if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) {
if (flowType === autoType || flowType === autoArrayType) {
if (noImplicitAny) {
error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType));
error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType));
}
return convertAutoToAny(flowType);
}
}
else if (!assumeInitialized && !containsUndefinedType(type) && containsUndefinedType(flowType)) {
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
// Return the declared type to reduce follow-on errors
return type;
}
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
}
function isSameScopedBindingElement(node: Identifier, declaration: Declaration) {
if (isBindingElement(declaration)) {
const bindingElement = findAncestor(node, isBindingElement);
return bindingElement && getRootDeclaration(bindingElement) === getRootDeclaration(declaration);
}
}
function shouldMarkIdentifierAliasReferenced(node: Identifier): boolean {
const parent = node.parent;
if (parent) {
// A property access expression LHS? checkPropertyAccessExpression will handle that.
if (isPropertyAccessExpression(parent) && parent.expression === node) {
return false;
}
// Next two check for an identifier inside a type only export.
if (isExportSpecifier(parent) && parent.isTypeOnly) {
return false;
}
const greatGrandparent = parent.parent?.parent;
if (greatGrandparent && isExportDeclaration(greatGrandparent) && greatGrandparent.isTypeOnly) {
return false;
}
}
return true;
}
function isInsideFunctionOrInstancePropertyInitializer(node: Node, threshold: Node): boolean {
return !!findAncestor(node, n =>
n === threshold ? "quit" : isFunctionLike(n) || (
n.parent && isPropertyDeclaration(n.parent) && !hasStaticModifier(n.parent) && n.parent.initializer === n
));
}
function getPartOfForStatementContainingNode(node: Node, container: ForStatement) {
return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement);
}
function getEnclosingIterationStatement(node: Node): Node | undefined {
return findAncestor(node, n => (!n || nodeStartsNewLexicalEnvironment(n)) ? "quit" : isIterationStatement(n, /*lookInLabeledStatements*/ false));
}
function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void {
if (
languageVersion >= ScriptTarget.ES2015 ||
(symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 ||
!symbol.valueDeclaration ||
isSourceFile(symbol.valueDeclaration) ||
symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause
) {
return;
}
// 1. walk from the use site up to the declaration and check
// if there is anything function like between declaration and use-site (is binding/class is captured in function).
// 2. walk from the declaration up to the boundary of lexical environment and check
// if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement)
const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
const isCaptured = isInsideFunctionOrInstancePropertyInitializer(node, container);
const enclosingIterationStatement = getEnclosingIterationStatement(container);
if (enclosingIterationStatement) {
if (isCaptured) {
// mark iteration statement as containing block-scoped binding captured in some function
let capturesBlockScopeBindingInLoopBody = true;
if (isForStatement(container)) {
const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList);
if (varDeclList && varDeclList.parent === container) {
const part = getPartOfForStatementContainingNode(node.parent, container);
if (part) {
const links = getNodeLinks(part);
links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding;
const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []);
pushIfUnique(capturedBindings, symbol);
if (part === container.initializer) {
capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body
}
}
}
}
if (capturesBlockScopeBindingInLoopBody) {
getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
}
}
// mark variables that are declared in loop initializer and reassigned inside the body of ForStatement.
// if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back.
if (isForStatement(container)) {
const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList);
if (varDeclList && varDeclList.parent === container && isAssignedInBodyOfForStatement(node, container)) {
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter;
}
}
// set 'declared inside loop' bit on the block-scoped binding
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
}
if (isCaptured) {
getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding;
}
}
function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) {
const links = getNodeLinks(node);
return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfDeclaration(decl));
}
function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean {
// skip parenthesized nodes
let current: Node = node;
while (current.parent.kind === SyntaxKind.ParenthesizedExpression) {
current = current.parent;
}
// check if node is used as LHS in some assignment expression
let isAssigned = false;
if (isAssignmentTarget(current)) {
isAssigned = true;
}
else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) {
const expr = current.parent as PrefixUnaryExpression | PostfixUnaryExpression;
isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken;
}
if (!isAssigned) {
return false;
}
// at this point we know that node is the target of assignment
// now check that modification happens inside the statement part of the ForStatement
return !!findAncestor(current, n => n === container ? "quit" : n === container.statement);
}
function captureLexicalThis(node: Node, container: Node): void {
getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis;
if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) {
const classNode = container.parent;
getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis;
}
else {
getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis;
}
}
function findFirstSuperCall(node: Node): SuperCall | undefined {
return isSuperCall(node) ? node :
isFunctionLike(node) ? undefined :
forEachChild(node, findFirstSuperCall);
}
/**
* Check if the given class-declaration extends null then return true.
* Otherwise, return false
* @param classDecl a class declaration to check if it extends null
*/
function classDeclarationExtendsNull(classDecl: ClassLikeDeclaration): boolean {
const classSymbol = getSymbolOfDeclaration(classDecl);
const classInstanceType = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType;
const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType);
return baseConstructorType === nullWideningType;
}
function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) {
const containingClassDecl = container.parent as ClassDeclaration;
const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl);
// If a containing class does not have extends clause or the class extends null
// skip checking whether super statement is called before "this" accessing.
if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) {
if (canHaveFlowNode(node) && node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) {
error(node, diagnosticMessage);
}
}
}
function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) {
if (
isPropertyDeclaration(container) && hasStaticModifier(container) && legacyDecorators &&
container.initializer && textRangeContainsPositionInclusive(container.initializer, thisExpression.pos) && hasDecorators(container.parent)
) {
error(thisExpression, Diagnostics.Cannot_use_this_in_a_static_property_initializer_of_a_decorated_class);
}
}
function checkThisExpression(node: Node): Type {
const isNodeInTypeQuery = isInTypeQuery(node);
// Stop at the first arrow function so that we can
// tell whether 'this' needs to be captured.
let container = getThisContainer(node, /*includeArrowFunctions*/ true, /*includeClassComputedPropertyName*/ true);
let capturedByArrowFunction = false;
let thisInComputedPropertyName = false;
if (container.kind === SyntaxKind.Constructor) {
checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class);
}
while (true) {
// Now skip arrow functions to get the "real" owner of 'this'.
if (container.kind === SyntaxKind.ArrowFunction) {
container = getThisContainer(container, /*includeArrowFunctions*/ false, !thisInComputedPropertyName);
capturedByArrowFunction = true;
}
if (container.kind === SyntaxKind.ComputedPropertyName) {
container = getThisContainer(container, !capturedByArrowFunction, /*includeClassComputedPropertyName*/ false);
thisInComputedPropertyName = true;
continue;
}
break;
}
checkThisInStaticClassFieldInitializerInDecoratedClass(node, container);
if (thisInComputedPropertyName) {
error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name);
}
else {
switch (container.kind) {
case SyntaxKind.ModuleDeclaration:
error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body);
// do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks
break;
case SyntaxKind.EnumDeclaration:
error(node, Diagnostics.this_cannot_be_referenced_in_current_location);
// do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks
break;
}
}
// When targeting es6, mark that we'll need to capture `this` in its lexically bound scope.
if (!isNodeInTypeQuery && capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) {
captureLexicalThis(node, container);
}
const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container);
if (noImplicitThis) {
const globalThisType = getTypeOfSymbol(globalThisSymbol);
if (type === globalThisType && capturedByArrowFunction) {
error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this);
}
else if (!type) {
// With noImplicitThis, functions may not reference 'this' if it has type 'any'
const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation);
if (!isSourceFile(container)) {
const outsideThis = tryGetThisTypeAt(container);
if (outsideThis && outsideThis !== globalThisType) {
addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container));
}
}
}
}
return type || anyType;
}
function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)): Type | undefined {
const isInJS = isInJSFile(node);
if (
isFunctionLike(container) &&
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))
) {
let thisType = getThisTypeOfDeclaration(container) || isInJS && getTypeForThisExpressionFromJSDoc(container);
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
// If this is a function in a JS file, it might be a class method.
if (!thisType) {
const className = getClassNameFromPrototypeMethod(container);
if (isInJS && className) {
const classSymbol = checkExpression(className).symbol;
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
thisType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType;
}
}
else if (isJSConstructor(container)) {
thisType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType;
}
thisType ||= getContextualThisParameterType(container);
}
if (thisType) {
return getFlowTypeOfReference(node, thisType);
}
}
if (isClassLike(container.parent)) {
const symbol = getSymbolOfDeclaration(container.parent);
const type = isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!;
return getFlowTypeOfReference(node, type);
}
if (isSourceFile(container)) {
// look up in the source file's locals or exports
if (container.commonJsModuleIndicator) {
const fileSymbol = getSymbolOfDeclaration(container);
return fileSymbol && getTypeOfSymbol(fileSymbol);
}
else if (container.externalModuleIndicator) {
// TODO: Maybe issue a better error than 'object is possibly undefined'
return undefinedType;
}
else if (includeGlobalThis) {
return getTypeOfSymbol(globalThisSymbol);
}
}
}
function getExplicitThisType(node: Expression) {
const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false);
if (isFunctionLike(container)) {
const signature = getSignatureFromDeclaration(container);
if (signature.thisParameter) {
return getExplicitTypeOfSymbol(signature.thisParameter);
}
}
if (isClassLike(container.parent)) {
const symbol = getSymbolOfDeclaration(container.parent);
return isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!;
}
}
function getClassNameFromPrototypeMethod(container: Node) {
// Check if it's the RHS of a x.prototype.y = function [name]() { .... }
if (
container.kind === SyntaxKind.FunctionExpression &&
isBinaryExpression(container.parent) &&
getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty
) {
// Get the 'x' of 'x.prototype.y = container'
return ((container.parent // x.prototype.y = container
.left as PropertyAccessExpression) // x.prototype.y
.expression as PropertyAccessExpression) // x.prototype
.expression; // x
}
// x.prototype = { method() { } }
else if (
container.kind === SyntaxKind.MethodDeclaration &&
container.parent.kind === SyntaxKind.ObjectLiteralExpression &&
isBinaryExpression(container.parent.parent) &&
getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype
) {
return (container.parent.parent.left as PropertyAccessExpression).expression;
}
// x.prototype = { method: function() { } }
else if (
container.kind === SyntaxKind.FunctionExpression &&
container.parent.kind === SyntaxKind.PropertyAssignment &&
container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression &&
isBinaryExpression(container.parent.parent.parent) &&
getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype
) {
return (container.parent.parent.parent.left as PropertyAccessExpression).expression;
}
// Object.defineProperty(x, "method", { value: function() { } });
// Object.defineProperty(x, "method", { set: (x: () => void) => void });
// Object.defineProperty(x, "method", { get: () => function() { }) });
else if (
container.kind === SyntaxKind.FunctionExpression &&
isPropertyAssignment(container.parent) &&
isIdentifier(container.parent.name) &&
(container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") &&
isObjectLiteralExpression(container.parent.parent) &&
isCallExpression(container.parent.parent.parent) &&
container.parent.parent.parent.arguments[2] === container.parent.parent &&
getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty
) {
return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression;
}
// Object.defineProperty(x, "method", { value() { } });
// Object.defineProperty(x, "method", { set(x: () => void) {} });
// Object.defineProperty(x, "method", { get() { return () => {} } });
else if (
isMethodDeclaration(container) &&
isIdentifier(container.name) &&
(container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") &&
isObjectLiteralExpression(container.parent) &&
isCallExpression(container.parent.parent) &&
container.parent.parent.arguments[2] === container.parent &&
getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty
) {
return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression;
}
}
function getTypeForThisExpressionFromJSDoc(node: SignatureDeclaration) {
const thisTag = getJSDocThisTag(node);
if (thisTag && thisTag.typeExpression) {
return getTypeFromTypeNode(thisTag.typeExpression);
}
const signature = getSignatureOfTypeTag(node);
if (signature) {
return getThisTypeOfSignature(signature);
}
}
function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean {
return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl);
}
function checkSuperExpression(node: Node): Type {
const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node;
const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true);
let container = immediateContainer;
let needToCaptureLexicalThis = false;
let inAsyncFunction = false;
// adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting
if (!isCallExpression) {
while (container && container.kind === SyntaxKind.ArrowFunction) {
if (hasSyntacticModifier(container, ModifierFlags.Async)) inAsyncFunction = true;
container = getSuperContainer(container, /*stopOnFunctions*/ true);
needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015;
}
if (container && hasSyntacticModifier(container, ModifierFlags.Async)) inAsyncFunction = true;
}
let nodeCheckFlag: NodeCheckFlags = 0;
if (!container || !isLegalUsageOfSuperExpression(container)) {
// issue more specific error if super is used in computed property name
// class A { foo() { return "1" }}
// class B {
// [super.foo()]() {}
// }
const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName);
if (current && current.kind === SyntaxKind.ComputedPropertyName) {
error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name);
}
else if (isCallExpression) {
error(node, Diagnostics.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors);
}
else if (!container || !container.parent || !(isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression)) {
error(node, Diagnostics.super_can_only_be_referenced_in_members_of_derived_classes_or_object_literal_expressions);
}
else {
error(node, Diagnostics.super_property_access_is_permitted_only_in_a_constructor_member_function_or_member_accessor_of_a_derived_class);
}
return errorType;
}
if (!isCallExpression && immediateContainer!.kind === SyntaxKind.Constructor) {
checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class);
}
if (isStatic(container) || isCallExpression) {
nodeCheckFlag = NodeCheckFlags.SuperStatic;
if (
!isCallExpression &&
languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 &&
(isPropertyDeclaration(container) || isClassStaticBlockDeclaration(container))
) {
// for `super.x` or `super[x]` in a static initializer, mark all enclosing
// block scope containers so that we can report potential collisions with
// `Reflect`.
forEachEnclosingBlockScopeContainer(node.parent, current => {
if (!isSourceFile(current) || isExternalOrCommonJsModule(current)) {
getNodeLinks(current).flags |= NodeCheckFlags.ContainsSuperPropertyInStaticInitializer;
}
});
}
}
else {
nodeCheckFlag = NodeCheckFlags.SuperInstance;
}
getNodeLinks(node).flags |= nodeCheckFlag;
// Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference.
// This is due to the fact that we emit the body of an async function inside of a generator function. As generator
// functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper
// uses an arrow function, which is permitted to reference `super`.
//
// There are two primary ways we can access `super` from within an async method. The first is getting the value of a property
// or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value
// of a property or indexed access, either as part of an assignment expression or destructuring assignment.
//
// The simplest case is reading a value, in which case we will emit something like the following:
//
// // ts
// ...
// async asyncMethod() {
// let x = await super.asyncMethod();
// return x;
// }
// ...
//
// // js
// ...
// asyncMethod() {
// const _super = Object.create(null, {
// asyncMethod: { get: () => super.asyncMethod },
// });
// return __awaiter(this, arguments, Promise, function *() {
// let x = yield _super.asyncMethod.call(this);
// return x;
// });
// }
// ...
//
// The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases
// are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment:
//
// // ts
// ...
// async asyncMethod(ar: Promise) {
// [super.a, super.b] = await ar;
// }
// ...
//
// // js
// ...
// asyncMethod(ar) {
// const _super = Object.create(null, {
// a: { get: () => super.a, set: (v) => super.a = v },
// b: { get: () => super.b, set: (v) => super.b = v }
// };
// return __awaiter(this, arguments, Promise, function *() {
// [_super.a, _super.b] = yield ar;
// });
// }
// ...
//
// Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments
// as a call expression cannot be used as the target of a destructuring assignment while a property access can.
//
// For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations.
if (container.kind === SyntaxKind.MethodDeclaration && inAsyncFunction) {
if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) {
getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync;
}
else {
getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAccessInAsync;
}
}
if (needToCaptureLexicalThis) {
// call expressions are allowed only in constructors so they should always capture correct 'this'
// super property access expressions can also appear in arrow functions -
// in this case they should also use correct lexical this
captureLexicalThis(node.parent, container);
}
if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) {
if (languageVersion < ScriptTarget.ES2015) {
error(node, Diagnostics.super_is_only_allowed_in_members_of_object_literal_expressions_when_option_target_is_ES2015_or_higher);
return errorType;
}
else {
// for object literal assume that type of 'super' is 'any'
return anyType;
}
}
// at this point the only legal case for parent is ClassLikeDeclaration
const classLikeDeclaration = container.parent as ClassLikeDeclaration;
if (!getClassExtendsHeritageElement(classLikeDeclaration)) {
error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class);
return errorType;
}
if (classDeclarationExtendsNull(classLikeDeclaration)) {
return isCallExpression ? errorType : nullWideningType;
}
const classType = getDeclaredTypeOfSymbol(getSymbolOfDeclaration(classLikeDeclaration)) as InterfaceType;
const baseClassType = classType && getBaseTypes(classType)[0];
if (!baseClassType) {
return errorType;
}
if (container.kind === SyntaxKind.Constructor && isInConstructorArgumentInitializer(node, container)) {
// issue custom error message for super property access in constructor arguments (to be aligned with old compiler)
error(node, Diagnostics.super_cannot_be_referenced_in_constructor_arguments);
return errorType;
}
return nodeCheckFlag === NodeCheckFlags.SuperStatic
? getBaseConstructorTypeOfClass(classType)
: getTypeWithThisArgument(baseClassType, classType.thisType);
function isLegalUsageOfSuperExpression(container: Node): boolean {
if (isCallExpression) {
// TS 1.0 SPEC (April 2014): 4.8.1
// Super calls are only permitted in constructors of derived classes
return container.kind === SyntaxKind.Constructor;
}
else {
// TS 1.0 SPEC (April 2014)
// 'super' property access is allowed
// - In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance
// - In a static member function or static member accessor
// topmost container must be something that is directly nested in the class declaration\object literal expression
if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) {
if (isStatic(container)) {
return container.kind === SyntaxKind.MethodDeclaration ||
container.kind === SyntaxKind.MethodSignature ||
container.kind === SyntaxKind.GetAccessor ||
container.kind === SyntaxKind.SetAccessor ||
container.kind === SyntaxKind.PropertyDeclaration ||
container.kind === SyntaxKind.ClassStaticBlockDeclaration;
}
else {
return container.kind === SyntaxKind.MethodDeclaration ||
container.kind === SyntaxKind.MethodSignature ||
container.kind === SyntaxKind.GetAccessor ||
container.kind === SyntaxKind.SetAccessor ||
container.kind === SyntaxKind.PropertyDeclaration ||
container.kind === SyntaxKind.PropertySignature ||
container.kind === SyntaxKind.Constructor;
}
}
}
return false;
}
}
function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined {
return (func.kind === SyntaxKind.MethodDeclaration ||
func.kind === SyntaxKind.GetAccessor ||
func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent :
func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent as ObjectLiteralExpression :
undefined;
}
function getThisTypeArgument(type: Type): Type | undefined {
return getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target === globalThisType ? getTypeArguments(type as TypeReference)[0] : undefined;
}
function getThisTypeFromContextualType(type: Type): Type | undefined {
return mapType(type, t => {
return t.flags & TypeFlags.Intersection ? forEach((t as IntersectionType).types, getThisTypeArgument) : getThisTypeArgument(t);
});
}
function getThisTypeOfObjectLiteralFromContextualType(containingLiteral: ObjectLiteralExpression, contextualType: Type | undefined) {
let literal = containingLiteral;
let type = contextualType;
while (type) {
const thisType = getThisTypeFromContextualType(type);
if (thisType) {
return thisType;
}
if (literal.parent.kind !== SyntaxKind.PropertyAssignment) {
break;
}
literal = literal.parent.parent as ObjectLiteralExpression;
type = getApparentTypeOfContextualType(literal, /*contextFlags*/ undefined);
}
}
function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined {
if (func.kind === SyntaxKind.ArrowFunction) {
return undefined;
}
if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
const thisParameter = contextualSignature.thisParameter;
if (thisParameter) {
return getTypeOfSymbol(thisParameter);
}
}
}
const inJs = isInJSFile(func);
if (noImplicitThis || inJs) {
const containingLiteral = getContainingObjectLiteral(func);
if (containingLiteral) {
// We have an object literal method. Check if the containing object literal has a contextual type
// that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in
// any directly enclosing object literals.
const contextualType = getApparentTypeOfContextualType(containingLiteral, /*contextFlags*/ undefined);
const thisType = getThisTypeOfObjectLiteralFromContextualType(containingLiteral, contextualType);
if (thisType) {
return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral)));
}
// There was no contextual ThisType for the containing object literal, so the contextual type
// for 'this' is the non-null form of the contextual type for the containing object literal or
// the type of the object literal itself.
return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral));
}
// In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the
// contextual type for 'this' is 'obj'.
const parent = walkUpParenthesizedExpressions(func.parent);
if (isAssignmentExpression(parent)) {
const target = parent.left;
if (isAccessExpression(target)) {
const { expression } = target;
// Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }`
if (inJs && isIdentifier(expression)) {
const sourceFile = getSourceFileOfNode(parent);
if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) {
return undefined;
}
}
return getWidenedType(checkExpressionCached(expression));
}
}
}
return undefined;
}
// Return contextual type of parameter or undefined if no contextual type is available
function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined {
const func = parameter.parent;
if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
return undefined;
}
const iife = getImmediatelyInvokedFunctionExpression(func);
if (iife && iife.arguments) {
const args = getEffectiveCallArguments(iife);
const indexOfParameter = func.parameters.indexOf(parameter);
if (parameter.dotDotDotToken) {
return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined, CheckMode.Normal);
}
const links = getNodeLinks(iife);
const cached = links.resolvedSignature;
links.resolvedSignature = anySignature;
const type = indexOfParameter < args.length ?
getWidenedLiteralType(checkExpression(args[indexOfParameter])) :
parameter.initializer ? undefined : undefinedWideningType;
links.resolvedSignature = cached;
return type;
}
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0);
return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ?
getRestTypeAtPosition(contextualSignature, index) :
tryGetTypeAtPosition(contextualSignature, index);
}
}
function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, contextFlags: ContextFlags | undefined): Type | undefined {
const typeNode = getEffectiveTypeAnnotationNode(declaration) || (isInJSFile(declaration) ? tryGetJSDocSatisfiesTypeNode(declaration) : undefined);
if (typeNode) {
return getTypeFromTypeNode(typeNode);
}
switch (declaration.kind) {
case SyntaxKind.Parameter:
return getContextuallyTypedParameterType(declaration);
case SyntaxKind.BindingElement:
return getContextualTypeForBindingElement(declaration, contextFlags);
case SyntaxKind.PropertyDeclaration:
if (isStatic(declaration)) {
return getContextualTypeForStaticPropertyDeclaration(declaration, contextFlags);
}
// By default, do nothing and return undefined - only the above cases have context implied by a parent
}
}
function getContextualTypeForBindingElement(declaration: BindingElement, contextFlags: ContextFlags | undefined): Type | undefined {
const parent = declaration.parent.parent;
const name = declaration.propertyName || declaration.name;
const parentType = getContextualTypeForVariableLikeDeclaration(parent, contextFlags) ||
parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent, declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal);
if (!parentType || isBindingPattern(name) || isComputedNonLiteralName(name)) return undefined;
if (parent.name.kind === SyntaxKind.ArrayBindingPattern) {
const index = indexOfNode(declaration.parent.elements, declaration);
if (index < 0) return undefined;
return getContextualTypeForElementExpression(parentType, index);
}
const nameType = getLiteralTypeFromPropertyName(name);
if (isTypeUsableAsPropertyName(nameType)) {
const text = getPropertyNameFromType(nameType);
return getTypeOfPropertyOfType(parentType, text);
}
}
function getContextualTypeForStaticPropertyDeclaration(declaration: PropertyDeclaration, contextFlags: ContextFlags | undefined): Type | undefined {
const parentType = isExpression(declaration.parent) && getContextualType(declaration.parent, contextFlags);
if (!parentType) return undefined;
return getTypeOfPropertyOfContextualType(parentType, getSymbolOfDeclaration(declaration).escapedName);
}
// In a variable, parameter or property declaration with a type annotation,
// the contextual type of an initializer expression is the type of the variable, parameter or property.
// Otherwise, in a parameter declaration of a contextually typed function expression,
// the contextual type of an initializer expression is the contextual type of the parameter.
// Otherwise, in a variable or parameter declaration with a binding pattern name,
// the contextual type of an initializer expression is the type implied by the binding pattern.
// Otherwise, in a binding pattern inside a variable or parameter declaration,
// the contextual type of an initializer expression is the type annotation of the containing declaration, if present.
function getContextualTypeForInitializerExpression(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined {
const declaration = node.parent as VariableLikeDeclaration;
if (hasInitializer(declaration) && node === declaration.initializer) {
const result = getContextualTypeForVariableLikeDeclaration(declaration, contextFlags);
if (result) {
return result;
}
if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name) && declaration.name.elements.length > 0) {
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false);
}
}
return undefined;
}
function getContextualTypeForReturnExpression(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined {
const func = getContainingFunction(node);
if (func) {
let contextualReturnType = getContextualReturnType(func, contextFlags);
if (contextualReturnType) {
const functionFlags = getFunctionFlags(func);
if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function
const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0;
if (contextualReturnType.flags & TypeFlags.Union) {
contextualReturnType = filterType(contextualReturnType, type => !!getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator));
}
const iterationReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0);
if (!iterationReturnType) {
return undefined;
}
contextualReturnType = iterationReturnType;
// falls through to unwrap Promise for AsyncGenerators
}
if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function
// Get the awaited type without the `Awaited