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[]) {
- 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);
- if (!(t.flags & TypeFlags.Never)) intersections.push(t);
- }
- return intersections;
- }
-
- function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
- const links = getNodeLinks(node);
- if (!links.resolvedType) {
- const aliasSymbol = getAliasSymbolForTypeNode(node);
- links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode),
- aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol));
- }
- return links.resolvedType;
- }
-
- function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) {
- const result = createType(TypeFlags.Index) as IndexType;
- result.type = type;
- result.stringsOnly = stringsOnly;
- return result;
- }
-
- function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) {
- const result = createOriginType(TypeFlags.Index) as IndexType;
- result.type = type;
- return result;
- }
-
- function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) {
- return stringsOnly ?
- type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) :
- type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false));
- }
-
- /**
- * 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, stringsOnly: boolean, noIndexSignatures: boolean | undefined) {
- const typeParameter = getTypeParameterFromMappedType(type);
- const constraintType = getConstraintTypeFromMappedType(type);
- const nameType = getNameTypeFromMappedType(type.target as MappedType || type);
- if (!nameType && !noIndexSignatures) {
- // no mapping and no filtering required, just quickly bail to returning the constraint in the common case
- return constraintType;
- }
- const keyTypes: Type[] = [];
- if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
- // We have a { [P in keyof T]: X }
-
- // `getApparentType` on the T in a generic mapped type can trigger a circularity
- // (conditionals and `infer` types create a circular dependency in the constraint resolution)
- // so we only eagerly manifest the keys if the constraint is nongeneric
- if (!isGenericIndexType(constraintType)) {
- const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
- forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, stringsOnly, addMemberForKeyType);
- }
- else {
- // 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, stringsOnly);
- }
- }
- else {
- forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
- }
- if (isGenericIndexType(constraintType)) { // include the generic component in the resulting type
- forEachType(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 = 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).substitute) :
- type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) :
- false;
- }
- }
-
- function getLiteralTypeFromPropertyName(name: PropertyName) {
- if (isPrivateIdentifier(name)) {
- return neverType;
- }
- return isIdentifier(name) ? getStringLiteralType(unescapeLeadingUnderscores(name.escapedText)) :
- getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name));
- }
-
- 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;
- 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 getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type {
- type = getReducedType(type);
- return type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
- type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
- type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) :
- getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) :
- type === wildcardType ? wildcardType :
- type.flags & TypeFlags.Unknown ? neverType :
- type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
- getLiteralTypeFromProperties(type, (noIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (stringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike),
- stringsOnly === keyofStringsOnly && !noIndexSignatures);
- }
-
- function getExtractStringType(type: Type) {
- if (keyofStringsOnly) {
- return 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:
- throw 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 === "") && every(newTypes, t => !!(t.flags & TypeFlags.String))) {
- return stringType;
- }
- 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)) :
- isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) :
- type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) :
- 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 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 = createType(TypeFlags.StringMapping) as StringMappingType;
- result.symbol = symbol;
- 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: StringLiteral | Identifier | PrivateIdentifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | 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) || !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated));
- }
- 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 && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && 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 = getTypeOfSymbol(prop);
- return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ?
- getFlowTypeOfReference(accessExpression, propType) :
- propType;
- }
- if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) {
- if (accessNode && everyType(objectType, t => !(t as TupleTypeReference).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) {
- const indexNode = getIndexNodeForAccessExpression(accessNode);
- if (isTupleType(objectType)) {
- 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));
- }
- }
- errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType));
- return mapType(objectType, t => {
- const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType;
- return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, undefinedType]) : restType;
- });
- }
- }
- 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) {
- 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, undefinedType]) : indexInfo.type;
- }
- errorIfWritingToReadonlyIndex(indexInfo);
- return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : 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 && !compilerOptions.suppressImplicitAnyIndexErrors && !(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(accessExpression, errorInfo));
- }
- }
- }
- }
- return undefined;
- }
- }
- if (isJSLiteralType(objectType)) {
- return anyType;
- }
- if (accessNode) {
- const indexNode = getIndexNodeForAccessExpression(accessNode);
- if (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 {
- error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType));
- }
- }
- 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) {
- return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt));
- }
-
- function isPatternLiteralType(type: Type) {
- return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType);
- }
-
- 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).substitute) | getGenericObjectFlags((type as SubstitutionType).baseType);
- }
- 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 | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0);
- }
-
- function isThisTypeParameter(type: Type): boolean {
- return !!(type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType);
- }
-
- 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.UnionOrIntersection) {
- const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing));
- return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types);
- }
- }
-
- function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) {
- // T[A | B] -> T[A] | T[B] (reading)
- // T[A | B] -> T[A] & T[B] (writing)
- if (indexType.flags & TypeFlags.Union) {
- const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing));
- return writing ? getIntersectionType(types) : getUnionType(types);
- }
- }
-
- // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
- // the type itself if no transformation is possible. 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, 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)) {
- return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing));
- }
- return type[cache] = type;
- }
-
- function isConditionalTypeAlwaysTrueDisregardingInferTypes(type: ConditionalType) {
- const extendsInferParamMapper = type.root.inferTypeParameters && createTypeMapper(type.root.inferTypeParameters, map(type.root.inferTypeParameters, () => wildcardType));
- const checkType = type.checkType;
- const extendsType = type.extendsType;
- return isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(instantiateType(extendsType, extendsInferParamMapper)));
- }
-
- 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);
- }
-
- function substituteIndexedMappedType(objectType: MappedType, index: Type) {
- const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]);
- const templateMapper = combineTypeMappers(objectType.mapper, mapper);
- return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper);
- }
-
- 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;
- }
- // 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, objectType.target.fixedLength) :
- isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)))) {
- 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, 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);
- const resolved = getIndexedAccessType(objectType, indexType, AccessFlags.None, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias));
- links.resolvedType = resolved.flags & TypeFlags.IndexedAccess &&
- (resolved as IndexedAccessType).objectType === objectType &&
- (resolved as IndexedAccessType).indexType === indexType ?
- getConditionalFlowTypeOfType(resolved, node) : resolved;
- }
- 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 (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 isTypicalNondistributiveConditional(root: ConditionalRoot) {
- return !root.isDistributive && isSingletonTupleType(root.node.checkType) && isSingletonTupleType(root.node.extendsType);
- }
-
- function isSingletonTupleType(node: TypeNode) {
- return isTupleTypeNode(node) && length(node.elements) === 1 && !isOptionalTypeNode(node.elements[0]) && !isRestTypeNode(node.elements[0]);
- }
-
- /**
- * We syntactually check for common nondistributive conditional shapes and unwrap them into
- * the intended comparison - we do this so we can check if the unwrapped types are generic or
- * not and appropriately defer condition calculation
- */
- function unwrapNondistributiveConditionalTuple(root: ConditionalRoot, type: Type) {
- return isTypicalNondistributiveConditional(root) && isTupleType(type) ? getTypeArguments(type)[0] : type;
- }
-
- function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, 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);
- result = errorType;
- break;
- }
- const isUnwrapped = isTypicalNondistributiveConditional(root);
- const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper);
- const checkTypeInstantiable = isGenericType(checkType);
- const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper);
- if (checkType === wildcardType || extendsType === wildcardType) {
- return wildcardType;
- }
- let combinedMapper: TypeMapper | undefined;
- if (root.inferTypeParameters) {
- const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None);
- if (!checkTypeInstantiable) {
- // 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(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType;
- // We attempt to resolve the conditional type only when the check and extends types are non-generic
- if (!checkTypeInstantiable && !isGenericType(inferredExtendsType)) {
- // 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 && !isUnwrapped) || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) {
- // Return union of trueType and falseType for 'any' since it matches anything
- if (checkType.flags & TypeFlags.Any && !isUnwrapped) {
- (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 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);
- 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(getSymbolOfNode(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 (node.isTypeOf && node.typeArguments) { // Only the non-typeof form can make use of type arguments
- error(node, Diagnostics.Type_arguments_cannot_be_used_here);
- links.resolvedSymbol = unknownSymbol;
- return links.resolvedType = errorType;
- }
- 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 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 next = node.isTypeOf
- ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText)
- : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning);
- 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 getTypeOfSymbol(symbol); // 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: TypeNode): Type {
- const links = getNodeLinks(node);
- if (!links.resolvedType) {
- // Deferred resolution of members is handled by resolveObjectTypeMembers
- const aliasSymbol = getAliasSymbolForTypeNode(node);
- if (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) ? getSymbolOfNode(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.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true);
- result.declarations = prop.declarations;
- result.nameType = getSymbolLinks(prop).nameType;
- result.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);
- result.type = getUnionType([getTypeOfSymbol(leftProp), removeMissingOrUndefinedType(rightType)]);
- result.leftSpread = leftProp;
- result.rightSpread = rightProp;
- result.declarations = declarations;
- result.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.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop);
- result.declarations = prop.declarations;
- result.nameType = getSymbolLinks(prop).nameType;
- result.syntheticOrigin = prop;
- return result;
- }
-
- function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) {
- return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info;
- }
-
- function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol?: Symbol, regularType?: LiteralType) {
- const type = createType(flags) as LiteralType;
- type.symbol = symbol!;
- type.value = value;
- type.regularType = regularType || type;
- return type;
- }
-
- function getFreshTypeOfLiteralType(type: Type): Type {
- if (type.flags & TypeFlags.Literal) {
- if (!(type as LiteralType).freshType) {
- const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType);
- freshType.freshType = freshType;
- (type as LiteralType).freshType = freshType;
- }
- return (type as LiteralType).freshType;
- }
- return type;
- }
-
- function getRegularTypeOfLiteralType(type: Type): Type {
- return type.flags & TypeFlags.Literal ? (type as LiteralType).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.Literal) && (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 qualifier = typeof value === "string" ? "@" : "#";
- const key = enumId + qualifier + 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 = createType(TypeFlags.UniqueESSymbol) as UniqueESSymbolType;
- type.symbol = symbol;
- type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String;
- return type;
- }
-
- function getESSymbolLikeTypeForNode(node: Node) {
- if (isValidESSymbolDeclaration(node)) {
- const symbol = getSymbolOfNode(node);
- const links = getSymbolLinks(symbol);
- return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol));
- }
- return esSymbolType;
- }
-
- function getThisType(node: Node): Type {
- const container = getThisContainer(node, /*includeArrowFunctions*/ 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(getSymbolOfNode(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(getSymbolOfNode(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);
- 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.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 { kind: TypeMapKind.Simple, source, target };
- }
-
- function makeArrayTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper {
- return { kind: TypeMapKind.Array, sources, targets };
- }
-
- function makeFunctionTypeMapper(func: (t: Type) => Type): TypeMapper {
- return { kind: TypeMapKind.Function, func };
- }
-
- function makeCompositeTypeMapper(kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper {
- return { 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 {
- return makeFunctionTypeMapper(t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? unknownType : t);
- }
-
- 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 === unknownType ? tp : tp.restrictiveInstantiation || (
- tp.restrictiveInstantiation = createTypeParameter(tp.symbol),
- (tp.restrictiveInstantiation as TypeParameter).constraint = unknownType,
- 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 (links.type && !couldContainTypeVariables(links.type)) {
- // 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.
- 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.target = symbol;
- result.mapper = mapper;
- if (symbol.valueDeclaration) {
- result.valueDeclaration = symbol.valueDeclaration;
- }
- if (links.nameType) {
- result.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.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 ? [declaration] : type.symbol.declarations!;
- typeParameters = (target.objectFlags & ObjectFlags.Reference || 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) {
- const newMapper = createTypeMapper(typeParameters, typeArguments);
- 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);
- }
- 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 invening 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, 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:
- 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.
- // * 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), t => {
- if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) {
- if (!type.declaration.nameType) {
- if (isArrayType(t)) {
- return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
- }
- if (isGenericTupleType(t)) {
- return instantiateMappedGenericTupleType(t, type, typeVariable, mapper);
- }
- if (isTupleType(t)) {
- return instantiateMappedTupleType(t, type, prependTypeMapping(typeVariable, t, mapper));
- }
- }
- return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper));
- }
- return t;
- }, 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 getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) {
- return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state;
- }
-
- function instantiateMappedGenericTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) {
- // When a tuple type is generic (i.e. when it contains variadic elements), we want to eagerly map the
- // non-generic elements and defer mapping the generic elements. In order to facilitate this, we transform
- // M<[A, B?, ...T, ...C[]] into [...M<[A]>, ...M<[B?]>, ...M, ...M] and then rely on tuple type
- // normalization to resolve the non-generic parts of the resulting tuple.
- const elementFlags = tupleType.target.elementFlags;
- const elementTypes = map(getTypeArguments(tupleType), (t, i) => {
- const singleton = elementFlags[i] & ElementFlags.Variadic ? t :
- elementFlags[i] & ElementFlags.Rest ? createArrayType(t) :
- createTupleType([t], [elementFlags[i]]);
- // The singleton is never a generic tuple type, so it is safe to recurse here.
- return instantiateMappedType(mappedType, prependTypeMapping(typeVariable, singleton, mapper));
- });
- const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, getMappedTypeModifiers(mappedType));
- return createTupleType(elementTypes, map(elementTypes, _ => ElementFlags.Variadic), newReadonly);
- }
-
- 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 instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
- const elementFlags = tupleType.target.elementFlags;
- const elementTypes = map(getTypeArguments(tupleType), (_, i) =>
- instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper));
- const modifiers = getMappedTypeModifiers(mappedType);
- const newTupleModifiers = 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, modifiers);
- return contains(elementTypes, errorType) ? errorType :
- createTupleType(elementTypes, newTupleModifiers, newReadonly, tupleType.target.labeledElementDeclarations);
- }
-
- 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 {
- const result = createObjectType(type.objectFlags | 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;
- }
- result.target = type;
- result.mapper = mapper;
- result.aliasSymbol = aliasSymbol || type.aliasSymbol;
- result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper);
- return result;
- }
-
- function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, 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 = 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 ? 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)), aliasSymbol, aliasTypeArguments) :
- getConditionalType(root, newMapper, 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;
- }
- totalInstantiationCount++;
- instantiationCount++;
- instantiationDepth++;
- const result = instantiateTypeWorker(type, mapper, aliasSymbol, aliasTypeArguments);
- 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, 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), aliasSymbol, aliasTypeArguments);
- }
- if (flags & TypeFlags.Substitution) {
- const maybeVariable = instantiateType((type as SubstitutionType).baseType, mapper);
- if (maybeVariable.flags & TypeFlags.TypeVariable) {
- return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((type as SubstitutionType).substitute, mapper));
- }
- else {
- const sub = instantiateType((type as SubstitutionType).substitute, mapper);
- if (sub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) {
- return maybeVariable;
- }
- return sub;
- }
- }
- 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);
- }
-
- // 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 (!isFunctionDeclaration(node) || isInJSFile(node) && !!getTypeForDeclarationFromJSDocComment(node)) &&
- (hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node));
- }
-
- function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) {
- // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value.
- return !node.typeParameters && !getEffectiveReturnTypeNode(node) && !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body);
- }
-
- function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
- return (isInJSFile(func) && isFunctionDeclaration(func) || 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 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 a type variable with a base constraint that is derived from T,
- // 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.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) :
- target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) :
- 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: ESMap,
- errorNode: Node | undefined,
- expr: Expression | undefined,
- headMessage: DiagnosticMessage | undefined,
- containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
- errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | 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: ESMap,
- headMessage: DiagnosticMessage | undefined,
- containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
- errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | 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.JsxExpression:
- case SyntaxKind.ParenthesizedExpression:
- return elaborateError((node as 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: ESMap,
- headMessage: DiagnosticMessage | undefined,
- containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
- errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | 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: ESMap,
- containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
- errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
- ): boolean {
- // Don't elaborate blocks
- if (isBlock(node.body)) {
- return false;
- }
- // Or functions with annotated parameter types
- if (some(node.parameters, ts.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, /*message*/ 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) {
- next.contextualType = sourcePropType;
- try {
- return checkExpressionForMutableLocation(next, CheckMode.Contextual, sourcePropType);
- }
- finally {
- next.contextualType = undefined;
- }
- }
-
- 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: ESMap,
- containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
- errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
- ) {
- // Assignability failure - check each prop individually, and if that fails, fall back on the bad error span
- let reportedError = false;
- for (let status = iterator.next(); !status.done; status = iterator.next()) {
- const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.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;
- }
-
- function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
- if (!length(node.properties)) return;
- for (const prop of node.properties) {
- if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue;
- yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(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: ESMap,
- containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
- errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | 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;
- const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType);
- const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t));
- if (moreThanOneRealChildren) {
- if (arrayLikeTargetParts !== neverType) {
- const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal));
- const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic);
- result = elaborateElementwise(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(/*_dummy*/ undefined, 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);
- yield { errorNode: elem, innerExpression: elem, nameType };
- }
- }
-
- function elaborateArrayLiteral(
- node: ArrayLiteralExpression,
- source: Type,
- target: Type,
- relation: ESMap,
- containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
- errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
- ) {
- if (target.flags & TypeFlags.Primitive) 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
- const oldContext = node.contextualType;
- node.contextualType = target;
- try {
- const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true);
- node.contextualType = oldContext;
- if (isTupleLikeType(tupleizedType)) {
- return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer);
- }
- return false;
- }
- finally {
- node.contextualType = oldContext;
- }
- }
-
- function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator {
- if (!length(node.properties)) return;
- for (const prop of node.properties) {
- if (isSpreadAssignment(prop)) continue;
- const type = getLiteralTypeFromProperty(getSymbolOfNode(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: ESMap,
- containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
- errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
- ) {
- if (target.flags & TypeFlags.Primitive) 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 : 0, /*reportErrors*/ false,
- /*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False;
- }
-
- type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void;
-
- /**
- * Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any`
- */
- function isAnySignature(s: Signature) {
- return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 &&
- signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) &&
- isTypeAny(getReturnTypeOfSignature(s));
- }
-
- /**
- * 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 (isAnySignature(target)) {
- return Ternary.True;
- }
-
- const targetCount = getParameterCount(target);
- const sourceHasMoreParameters = !hasEffectiveRestParameter(target) &&
- (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount);
- if (sourceHasMoreParameters) {
- 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);
- }
- if (sourceRestType && targetRestType && sourceCount !== targetCount) {
- // We're not able to relate misaligned complex rest parameters
- return Ternary.False;
- }
-
- 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 ? getRestTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i);
- const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i);
- if (sourceType && targetType) {
- // 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 ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
- const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType));
- const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) &&
- (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
- 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) {
- 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)) {
- 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 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(sourceSymbol: Symbol, targetSymbol: Symbol, errorReporter?: ErrorReporter) {
- if (sourceSymbol === targetSymbol) {
- return true;
- }
- const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol);
- const entry = enumRelation.get(id);
- if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) {
- return !!(entry & RelationComparisonResult.Succeeded);
- }
- if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) {
- enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported);
- return false;
- }
- const targetEnumType = getTypeOfSymbol(targetSymbol);
- for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) {
- if (property.flags & SymbolFlags.EnumMember) {
- const targetProperty = getPropertyOfType(targetEnumType, property.escapedName);
- if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) {
- if (errorReporter) {
- errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property),
- typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType));
- enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported);
- }
- else {
- enumRelation.set(id, RelationComparisonResult.Failed);
- }
- return false;
- }
- }
- }
- enumRelation.set(id, RelationComparisonResult.Succeeded);
- return true;
- }
-
- function isSimpleTypeRelatedTo(source: Type, target: Type, relation: ESMap, errorReporter?: ErrorReporter) {
- const s = source.flags;
- const t = target.flags;
- if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) 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 && 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(getParentOfSymbol(source.symbol)!, getParentOfSymbol(target.symbol)!, errorReporter)) return true;
- }
- if (s & TypeFlags.Undefined && (!strictNullChecks || t & (TypeFlags.Undefined | TypeFlags.Void))) return true;
- if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true;
- if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true;
- if (relation === assignableRelation || relation === comparableRelation) {
- if (s & TypeFlags.Any) return true;
- // Type number or any numeric literal type is assignable to any numeric enum type or any
- // numeric enum literal type. This rule exists for backwards compatibility reasons because
- // bit-flag enum types sometimes look like literal enum types with numeric literal values.
- if (s & (TypeFlags.Number | TypeFlags.NumberLiteral) && !(s & TypeFlags.EnumLiteral) && (
- t & TypeFlags.Enum || relation === assignableRelation && t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true;
- }
- return false;
- }
-
- function isTypeRelatedTo(source: Type, target: Type, relation: ESMap) {
- 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) 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));
- 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) {
- let t = isFreshLiteralType(type) ? (type as FreshableType).regularType :
- getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).node ? createTypeReference((type as TypeReference).target, getTypeArguments(type as TypeReference)) :
- type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) :
- type.flags & TypeFlags.Substitution ? writing ? (type as SubstitutionType).baseType : (type as SubstitutionType).substitute :
- type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) :
- type;
- t = getSingleBaseForNonAugmentingSubtype(t) || t;
- if (t === type) break;
- type = t;
- }
- return 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: ESMap,
- errorNode: Node | undefined,
- headMessage?: DiagnosticMessage,
- containingMessageChain?: () => DiagnosticMessageChain | undefined,
- errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean },
- ): boolean {
-
- let errorInfo: DiagnosticMessageChain | undefined;
- let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined;
- let maybeKeys: string[];
- 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 lastSkippedInfo: [Type, Type] | undefined;
- let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = [];
- let inPropertyCheck = false;
-
- Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking");
-
- const result = isRelatedTo(source, target, RecursionFlags.Both, /*reportErrors*/ !!errorNode, headMessage);
- if (incompatibleStack.length) {
- reportIncompatibleStack();
- }
- if (overflow) {
- tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth });
- const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, 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(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;
- relatedInfo = saved.relatedInfo;
- }
-
- function captureErrorCalculationState() {
- return {
- errorInfo,
- lastSkippedInfo,
- incompatibleStack: incompatibleStack.slice(),
- overrideNextErrorInfo,
- relatedInfo: !relatedInfo ? undefined : relatedInfo.slice() as ([DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined)
- };
- }
-
- function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) {
- overrideNextErrorInfo++; // Suppress the next relation error
- lastSkippedInfo = undefined; // Reset skipped info cache
- incompatibleStack.push([message, arg0, arg1, arg2, arg3]);
- }
-
- function reportIncompatibleStack() {
- const stack = incompatibleStack;
- incompatibleStack = [];
- const info = lastSkippedInfo;
- lastSkippedInfo = undefined;
- if (stack.length === 1) {
- reportError(...stack[0]);
- if (info) {
- // Actually do the last relation error
- reportRelationError(/*headMessage*/ 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: typeof incompatibleStack = [];
- 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(/*headMessage*/ undefined, ...info);
- }
- }
-
- function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void {
- Debug.assert(!!errorNode);
- if (incompatibleStack.length) reportIncompatibleStack();
- if (message.elidedInCompatabilityPyramid) return;
- errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3);
- }
-
- 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.length) reportIncompatibleStack();
- const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target);
- let generalizedSource = source;
- let generalizedSourceType = sourceType;
-
- if (isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) {
- generalizedSource = getBaseTypeOfLiteralType(source);
- Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable");
- generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource);
- }
-
- if (target.flags & TypeFlags.TypeParameter) {
- 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(/*reportErrors*/ false) === 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 isTupleType(target) || isArrayType(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 {
- // 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 (isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined)) {
- return Ternary.True;
- }
- reportErrorResults(originalSource, originalTarget, Ternary.False, !!(getObjectFlags(originalSource) & ObjectFlags.JsxAttributes));
- 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) {
- return isIdenticalTo(source, target, 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;
- }
-
- // Try to see if we're relating something like `Foo` -> `Bar | null | undefined`.
- // If so, reporting the `null` and `undefined` in the type is hardly useful.
- // First, see if we're even relating an object type to a union.
- // Then see if the target is stripped down to a single non-union type.
- // Note
- // * We actually want to remove null and undefined naively here (rather than using getNonNullableType),
- // since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable`"
- // when dealing with generics.
- // * We also don't deal with primitive source types, since we already halt elaboration below.
- if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object &&
- (target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) {
- const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable);
- if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) {
- target = getNormalizedType(nullStrippedTarget, /*writing*/ true);
- }
- if (source === nullStrippedTarget) return Ternary.True;
- }
-
- if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) ||
- isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
-
- const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
- 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 && !(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));
- 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);
-
- let result = Ternary.False;
- const saveErrorInfo = captureErrorCalculationState();
-
- // 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.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) {
- result = getConstituentCount(source) * getConstituentCount(target) >= 4 ?
- recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck, recursionFlags) :
- structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck);
- }
- if (!result && !(source.flags & TypeFlags.Union) && (source.flags & (TypeFlags.StructuredOrInstantiable) || target.flags & TypeFlags.StructuredOrInstantiable)) {
- if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) {
- resetErrorInfo(saveErrorInfo);
- }
- }
- if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) {
- // 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)`
- const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union));
- if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) {
- if (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
- if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
- resetErrorInfo(saveErrorInfo);
- }
- }
- }
- }
-
- // For certain combinations involving intersections and optional, excess, or mismatched properties we need
- // an extra property check where the intersection is viewed as a single object. 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
- //
- // function foo(x: { a?: string }, y: T & { a: boolean }) {
- // x = y; // Mismatched property in source intersection
- // }
- //
- // We suppress recursive intersection property checks because they can generate lots of work when relating
- // recursive intersections that are structurally similar but not exactly identical. See #37854.
- if (result && !inPropertyCheck && (
- target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) ||
- isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) {
- inPropertyCheck = true;
- result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags);
- inPropertyCheck = false;
- }
-
- reportErrorResults(source, target, result, isComparingJsxAttributes);
- return result;
-
- function reportErrorResults(source: Type, target: Type, result: Ternary, isComparingJsxAttributes: boolean) {
- if (!result && reportErrors) {
- 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);
- 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 (isComparingJsxAttributes && 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 result;
- }
- }
- else {
- errorInfo = elaborateNeverIntersection(errorInfo, originalTarget);
- }
- if (!headMessage && maybeSuppress) {
- lastSkippedInfo = [source, target];
- // Used by, eg, missing property checking to replace the top-level message with a more informative one
- return result;
- }
- reportRelationError(headMessage, source, target);
- }
- }
- }
-
- 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 isIdenticalTo(source: Type, target: Type, recursionFlags: RecursionFlags): Ternary {
- if (source.flags !== target.flags) return Ternary.False;
- if (source.flags & TypeFlags.Singleton) return Ternary.True;
- traceUnionsOrIntersectionsTooLarge(source, target);
- if (source.flags & TypeFlags.UnionOrIntersection) {
- let result = eachTypeRelatedToSomeType(source as UnionOrIntersectionType, target as UnionOrIntersectionType);
- if (result) {
- result &= eachTypeRelatedToSomeType(target as UnionOrIntersectionType, source as UnionOrIntersectionType);
- }
- return result;
- }
- return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None, recursionFlags);
- }
-
- 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);
-
- errorNode = propDeclaration;
-
- const name = propDeclaration.name!;
- if (isIdentifier(name)) {
- suggestion = getSuggestionForNonexistentProperty(name, errorTarget);
- }
- }
- if (suggestion !== undefined) {
- reportError(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 {
- reportError(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 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);
- if (!related) {
- return Ternary.False;
- }
- result &= related;
- }
- return result;
- }
-
- function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary {
- const targetTypes = target.types;
- if (target.flags & TypeFlags.Union) {
- if (containsType(targetTypes, source)) {
- return Ternary.True;
- }
- const match = getMatchingUnionConstituentForType(target as UnionType, source);
- if (match) {
- const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false);
- if (related) {
- return related;
- }
- }
- }
- for (const type of targetTypes) {
- const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false);
- if (related) {
- return related;
- }
- }
- if (reportErrors) {
- const bestMatchingType = getBestMatchingType(source, target, isRelatedTo);
- isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], RecursionFlags.Target, /*reportErrors*/ true);
- }
- 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) {
- // As a builtin type, `undefined` is a very low type ID - making it almsot always first, making this a very fast check to see
- // if we need to strip `undefined` from the target
- 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 | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0), relation);
- const entry = relation.get(id);
- if (entry !== undefined) {
- if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) {
- // We are elaborating errors and the cached result is an unreported failure. The result will be reported
- // as a failure, and should be updated as a reported failure by the bottom of this function.
- }
- 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, makeFunctionTypeMapper(reportUnmeasurableMarkers));
- }
- if (saved & RelationComparisonResult.ReportsUnreliable) {
- instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers));
- }
- }
- return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
- }
- }
- if (!maybeKeys) {
- maybeKeys = [];
- sourceStack = [];
- targetStack = [];
- }
- else {
- // generate a key where all type parameter id positions are replaced with unconstrained type parameter ids
- // this isn't perfect - nested type references passed as type arguments will muck up the indexes and thus
- // prevent finding matches- but it should hit up the common cases
- const broadestEquivalentId = id.split(",").map(i => i.replace(/-\d+/g, (_match, offset: number) => {
- const index = length(id.slice(0, offset).match(/[-=]/g) || undefined);
- return `=${index}`;
- })).join(",");
- for (let i = 0; i < maybeCount; i++) {
- // If source and target are already being compared, consider them related with assumptions
- if (id === maybeKeys[i] || broadestEquivalentId === maybeKeys[i]) {
- return Ternary.Maybe;
- }
- }
- if (sourceDepth === 100 || targetDepth === 100) {
- overflow = true;
- return Ternary.False;
- }
- }
- const maybeStart = maybeCount;
- maybeKeys[maybeCount] = id;
- maybeCount++;
- if (recursionFlags & RecursionFlags.Source) {
- sourceStack[sourceDepth] = source;
- sourceDepth++;
- }
- if (recursionFlags & RecursionFlags.Target) {
- targetStack[targetDepth] = target;
- targetDepth++;
- }
- const saveExpandingFlags = expandingFlags;
- if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source;
- if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target;
- let originalHandler: typeof outofbandVarianceMarkerHandler;
- let propagatingVarianceFlags: RelationComparisonResult = 0;
- if (outofbandVarianceMarkerHandler) {
- originalHandler = outofbandVarianceMarkerHandler;
- outofbandVarianceMarkerHandler = onlyUnreliable => {
- propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable;
- return originalHandler!(onlyUnreliable);
- };
- }
-
- 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
- });
- }
-
- const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, intersectionState) : Ternary.Maybe;
- if (outofbandVarianceMarkerHandler) {
- outofbandVarianceMarkerHandler = originalHandler;
- }
- expandingFlags = saveExpandingFlags;
- if (recursionFlags & RecursionFlags.Source) {
- sourceDepth--;
- }
- if (recursionFlags & RecursionFlags.Target) {
- targetDepth--;
- }
- 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.
- for (let i = maybeStart; i < maybeCount; i++) {
- relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
- }
- }
- maybeCount = maybeStart;
- }
- }
- 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, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags);
- maybeCount = maybeStart;
- }
- return result;
- }
-
- function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
- tracing?.push(tracing.Phase.CheckTypes, "structuredTypeRelatedTo", { sourceId: source.id, targetId: target.id });
- const result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState);
- tracing?.pop();
- return result;
- }
-
- function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
- if (intersectionState & IntersectionState.PropertyCheck) {
- return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None);
- }
- if (intersectionState & IntersectionState.UnionIntersectionCheck) {
- // 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) {
- return relation === comparableRelation ?
- someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck) :
- eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck);
- }
- if (target.flags & TypeFlags.Union) {
- return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
- }
- if (target.flags & TypeFlags.Intersection) {
- return typeRelatedToEachType(getRegularTypeOfObjectLiteral(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, getBaseConstraintOrType);
- if (constraints !== (source as IntersectionType).types) {
- source = getIntersectionType(constraints);
- if (!(source.flags & TypeFlags.Intersection)) {
- return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false);
- }
- }
- }
- // Check to see if any constituents of the intersection are immediately related to the target.
- //
- // Don't report errors though. Checking whether a constituent is related to the source is not actually
- // useful and leads to some confusing error messages. Instead it is better to let the below checks
- // take care of this, or to not elaborate at all. For instance,
- //
- // - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
- //
- // - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
- // than to report that 'D' is not assignable to 'A' or 'B'.
- //
- // - For a primitive type or type parameter (such as 'number = A & B') there is no point in
- // breaking the intersection apart.
- return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source);
- }
- const flags = source.flags & target.flags;
- if (relation === identityRelation && !(flags & TypeFlags.Object)) {
- if (flags & TypeFlags.Index) {
- return isRelatedTo((source as IndexType).type, (target as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false);
- }
- let result = Ternary.False;
- if (flags & 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 (flags & 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 (flags & TypeFlags.Substitution) {
- return isRelatedTo((source as SubstitutionType).substitute, (target as SubstitutionType).substitute, RecursionFlags.Both, /*reportErrors*/ false);
- }
- return Ternary.False;
- }
-
- let result: Ternary;
- let originalErrorInfo: DiagnosticMessageChain | undefined;
- let varianceCheckFailed = false;
- const saveErrorInfo = captureErrorCalculationState();
-
- // 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 (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
- source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
- !(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
- const variances = getAliasVariances(source.aliasSymbol);
- if (variances === emptyArray) {
- return Ternary.Unknown;
- }
- const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, 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 (target.flags & 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;
- }
- }
- }
- }
- else if (target.flags & TypeFlags.Index) {
- const targetType = (target as IndexType).type;
- // A keyof S is related to a keyof T if T is related to S.
- if (source.flags & 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).stringsOnly), 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 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)))
- );
- // 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 (target.flags & TypeFlags.IndexedAccess) {
- if (source.flags & 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) {
- resetErrorInfo(saveErrorInfo);
- 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)) {
- 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)) {
- // 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, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true);
- 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 (target.flags & 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)) {
- resetErrorInfo(saveErrorInfo);
- return Ternary.Maybe;
- }
- const c = target as ConditionalType;
- // 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 && isConditionalTypeAlwaysTrueDisregardingInferTypes(c);
-
- // Instantiate with a replacement mapper if the conditional is distributive, replacing the check type with a clone of itself,
- // this way {x: string | number, y: string | number} -> (T extends T ? { x: T, y: T } : never) appropriately _fails_ when
- // T = string | number (since that will end up distributing and producing `{x: string, y: string} | {x: number, y: number}`,
- // to which `{x: string | number, y: string | number}` isn't assignable)
- let distributionMapper: TypeMapper | undefined;
- const checkVar = getActualTypeVariable(c.root.checkType);
- if (c.root.isDistributive && checkVar.flags & TypeFlags.TypeParameter) {
- const newParam = cloneTypeParameter(checkVar);
- distributionMapper = prependTypeMapping(checkVar, newParam, c.mapper);
- newParam.mapper = distributionMapper;
- }
-
- // TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't)
- const expanding = isDeeplyNestedType(target, targetStack, targetDepth);
- let localResult: Ternary | undefined = expanding ? Ternary.Maybe : undefined;
- if (skipTrue || expanding || (localResult = isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.trueType), distributionMapper) : getTrueTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false))) {
- if (!skipFalse && !expanding) {
- localResult = (localResult || Ternary.Maybe) & isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.falseType), distributionMapper) : getFalseTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false);
- }
- }
- if (localResult) {
- resetErrorInfo(saveErrorInfo);
- return localResult;
- }
- }
- else if (target.flags & TypeFlags.TemplateLiteral) {
- if (source.flags & 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, makeFunctionTypeMapper(reportUnreliableMarkers));
- }
- const result = inferTypesFromTemplateLiteralType(source, target as TemplateLiteralType);
- if (result && every(result, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, (target as TemplateLiteralType).types[i]))) {
- return Ternary.True;
- }
- }
-
- if (source.flags & TypeFlags.TypeVariable) {
- // IndexedAccess comparisons are handled above in the `target.flags & TypeFlage.IndexedAccess` branch
- if (!(source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess)) {
- const constraint = getConstraintOfType(source as TypeVariable);
- if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
- // A type variable with no constraint is not related to the non-primitive object type.
- if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive), RecursionFlags.Both)) {
- resetErrorInfo(saveErrorInfo);
- return result;
- }
- }
- // hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
- else if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) {
- resetErrorInfo(saveErrorInfo);
- return result;
- }
- // slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example
- else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && !(target.flags & source.flags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) {
- resetErrorInfo(saveErrorInfo);
- return result;
- }
- }
- }
- else if (source.flags & TypeFlags.Index) {
- if (result = isRelatedTo(keyofConstraintType, target, RecursionFlags.Source, reportErrors)) {
- resetErrorInfo(saveErrorInfo);
- return result;
- }
- }
- else if (source.flags & TypeFlags.TemplateLiteral && !(target.flags & TypeFlags.Object)) {
- if (!(target.flags & TypeFlags.TemplateLiteral)) {
- const constraint = getBaseConstraintOfType(source);
- if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) {
- resetErrorInfo(saveErrorInfo);
- return result;
- }
- }
- }
- else if (source.flags & TypeFlags.StringMapping) {
- if (target.flags & TypeFlags.StringMapping && (source as StringMappingType).symbol === (target as StringMappingType).symbol) {
- if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) {
- resetErrorInfo(saveErrorInfo);
- return result;
- }
- }
- else {
- const constraint = getBaseConstraintOfType(source);
- if (constraint && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) {
- resetErrorInfo(saveErrorInfo);
- return result;
- }
- }
- }
- else if (source.flags & 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)) {
- resetErrorInfo(saveErrorInfo);
- return Ternary.Maybe;
- }
- if (target.flags & 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) {
- resetErrorInfo(saveErrorInfo);
- return result;
- }
- }
- }
- else {
- // 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 = getConstraintOfDistributiveConditionalType(source as ConditionalType);
- if (distributiveConstraint) {
- if (result = isRelatedTo(distributiveConstraint, target, RecursionFlags.Source, reportErrors)) {
- resetErrorInfo(saveErrorInfo);
- 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)) {
- resetErrorInfo(saveErrorInfo);
- 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)) {
- resetErrorInfo(saveErrorInfo);
- return result;
- }
- }
- return Ternary.False;
- }
- const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive);
- if (relation !== identityRelation) {
- source = getApparentType(source);
- }
- 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) && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) {
- // 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) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.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;
- }
- }
- // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})`
- // and not `{} <- fresh({}) <- {[idx: string]: any}`
- 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 (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & 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, intersectionState);
- if (result) {
- result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors);
- if (result) {
- result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors);
- 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 (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & 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);
- }
- }
- }
-
- function reportUnmeasurableMarkers(p: TypeParameter) {
- if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) {
- outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false);
- }
- return p;
- }
-
- function reportUnreliableMarkers(p: TypeParameter) {
- if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) {
- outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true);
- }
- return p;
- }
-
- // 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), makeFunctionTypeMapper(getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers));
- 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, IntersectionState.None);
- if (result) {
- result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false);
- if (result) {
- result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false);
- 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, /*reportStructuralErrors*/ 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);
- 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;
- }
- // 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.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);
- 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, intersectionState: IntersectionState): Ternary {
- if (relation === identityRelation) {
- return propertiesIdenticalTo(source, target, excludedProperties);
- }
- let result = Ternary.True;
- if (isTupleType(target)) {
- if (isArrayType(source) || isTupleType(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 targetRestFlag = target.target.combinedFlags & ElementFlags.Rest;
- 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 (!targetRestFlag && targetArity < sourceMinLength) {
- if (reportErrors) {
- reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity);
- }
- return Ternary.False;
- }
- if (!targetRestFlag && (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 startCount = Math.min(isTupleType(source) ? getStartElementCount(source.target, ElementFlags.NonRest) : 0, getStartElementCount(target.target, ElementFlags.NonRest));
- const endCount = Math.min(isTupleType(source) ? getEndElementCount(source.target, ElementFlags.NonRest) : 0, targetRestFlag ? getEndElementCount(target.target, ElementFlags.NonRest) : 0);
- let canExcludeDiscriminants = !!excludedProperties;
- for (let i = 0; i < targetArity; i++) {
- const sourceIndex = i < targetArity - endCount ? i : i + sourceArity - targetArity;
- const sourceFlags = isTupleType(source) && (i < startCount || i >= targetArity - endCount) ? source.target.elementFlags[sourceIndex] : ElementFlags.Rest;
- const targetFlags = target.target.elementFlags[i];
- if (targetFlags & ElementFlags.Variadic && !(sourceFlags & ElementFlags.Variadic)) {
- if (reportErrors) {
- reportError(Diagnostics.Source_provides_no_match_for_variadic_element_at_position_0_in_target, i);
- }
- 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, sourceIndex, i);
- }
- 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, i);
- }
- 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(("" + i) as __String)) {
- continue;
- }
- }
- const sourceType = !isTupleType(source) ? sourceTypeArguments[0] :
- i < startCount || i >= targetArity - endCount ? removeMissingType(sourceTypeArguments[sourceIndex], !!(sourceFlags & targetFlags & ElementFlags.Optional)) :
- getElementTypeOfSliceOfTupleType(source, startCount, endCount) || neverType;
- const targetType = targetTypeArguments[i];
- 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 (i < startCount || i >= targetArity - endCount || sourceArity - startCount - endCount === 1) {
- reportIncompatibleError(Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, sourceIndex, i);
- }
- else {
- reportIncompatibleError(Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, startCount, sourceArity - endCount - 1, i);
- }
- }
- 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) {
- 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")) {
- 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): 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 saveErrorInfo = captureErrorCalculationState();
- 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) {
- // 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.
- for (let i = 0; i < targetSignatures.length; i++) {
- const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, 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 || !!compilerOptions.noStrictGenericChecks;
- const sourceSignature = first(sourceSignatures);
- const targetSignature = first(targetSignatures);
- result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, 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) {
- // Only elaborate errors from the first failure
- let shouldElaborateErrors = reportErrors;
- for (const s of sourceSignatures) {
- const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, 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 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, incompatibleReporter: (source: Type, target: Type) => void): Ternary {
- return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target,
- relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, makeFunctionTypeMapper(reportUnreliableMarkers));
- }
-
- 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): 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);
- 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);
- if (!related) {
- return Ternary.False;
- }
- result &= related;
- }
- }
- return result;
- }
-
- function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) {
- const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors);
- 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 = !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);
- }
- if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) {
- // Intersection constituents are never considered to have an inferred index signature
- return membersRelatedToIndexInfo(source, targetInfo, reportErrors);
- }
- 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);
- }
-
- 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, /*skipPartial*/ true) ||
- findMatchingTypeReferenceOrTypeAliasReference(source, target) ||
- findBestTypeForObjectLiteral(source, target) ||
- findBestTypeForInvokable(source, target) ||
- findMostOverlappyType(source, target);
- }
-
- function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined;
- function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type;
- function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) {
- // undefined=unknown, true=discriminated, false=not discriminated
- // The state of each type progresses from left to right. Discriminated types stop at 'true'.
- const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[];
- for (const [getDiscriminatingType, propertyName] of discriminators) {
- const targetProp = getUnionOrIntersectionProperty(target, propertyName);
- if (skipPartial && targetProp && getCheckFlags(targetProp) & CheckFlags.ReadPartial) {
- continue;
- }
- let i = 0;
- for (const type of target.types) {
- const targetType = getTypeOfPropertyOfType(type, propertyName);
- if (targetType && related(getDiscriminatingType(), targetType)) {
- discriminable[i] = discriminable[i] === undefined ? true : discriminable[i];
- }
- else {
- discriminable[i] = false;
- }
- i++;
- }
- }
- const match = discriminable.indexOf(/*searchElement*/ true);
- if (match === -1) {
- return defaultValue;
- }
- // make sure exactly 1 matches before returning it
- let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1);
- while (nextMatch !== -1) {
- if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) {
- return defaultValue;
- }
- nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1);
- }
- return target.types[match];
- }
-
- /**
- * 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.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;
- }
-
- // Return a type reference where the source type parameter is replaced with the target marker
- // type, and flag the result as a marker type reference.
- function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: Type) {
- const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t));
- result.objectFlags |= ObjectFlags.MarkerType;
- return result;
- }
-
- function getAliasVariances(symbol: Symbol) {
- const links = getSymbolLinks(symbol);
- return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => {
- const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker)));
- type.aliasTypeArgumentsContainsMarker = true;
- return type;
- });
- }
-
- // 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(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] {
- let variances = cache.variances;
- if (!variances) {
- tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: (cache as any).id ?? (cache as any).declaredType?.id ?? -1 });
- // The emptyArray singleton is used to signal a recursive invocation.
- cache.variances = emptyArray;
- variances = [];
- for (const tp of typeParameters) {
- 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(cache, tp, markerSuperType);
- const typeWithSub = createMarkerType(cache, tp, markerSubType);
- let 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(cache, tp, markerOtherType), typeWithSuper)) {
- variance = VarianceFlags.Independent;
- }
- outofbandVarianceMarkerHandler = oldHandler;
- if (unmeasurable || unreliable) {
- if (unmeasurable) {
- variance |= VarianceFlags.Unmeasurable;
- }
- if (unreliable) {
- variance |= VarianceFlags.Unreliable;
- }
- }
- variances.push(variance);
- }
- cache.variances = variances;
- tracing?.pop();
- }
- return variances;
- }
-
- function getVariances(type: GenericType): VarianceFlags[] {
- // Arrays and tuples are known to be covariant, no need to spend time computing this.
- if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) {
- return arrayVariances;
- }
- return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference);
- }
-
- // 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));
- }
-
- /**
- * getTypeReferenceId(A) returns "111=0-12=1"
- * where A.id=111 and number.id=12
- */
- function getTypeReferenceId(type: TypeReference, typeParameters: Type[], depth = 0) {
- let result = "" + type.target.id;
- for (const t of getTypeArguments(type)) {
- if (isUnconstrainedTypeParameter(t)) {
- let index = typeParameters.indexOf(t);
- if (index < 0) {
- index = typeParameters.length;
- typeParameters.push(t);
- }
- result += "=" + index;
- }
- else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) {
- result += "<" + getTypeReferenceId(t as TypeReference, typeParameters, depth + 1) + ">";
- }
- else {
- 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: ESMap) {
- if (relation === identityRelation && source.id > target.id) {
- const temp = source;
- source = target;
- target = temp;
- }
- const postFix = intersectionState ? ":" + intersectionState : "";
- if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) {
- const typeParameters: Type[] = [];
- return getTypeReferenceId(source as TypeReference, typeParameters) + "," + getTypeReferenceId(target as TypeReference, typeParameters) + postFix;
- }
- return 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) {
- for (const t of (prop as TransientSymbol).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: Type, 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 structural type comparisons
- // for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible,
- // though highly unlikely, for this test 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 5
- // levels, but unequal at some level beyond that.
- // In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially
- // the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives
- // for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`).
- // It also detects when a recursive type reference has expanded 5 or more times, eg, if the true branch of
- // `type A = null extends T ? [A>] : [T]`
- // has expanded into `[A>>>>>]`
- // in such cases we need to terminate the expansion, and we do so here.
- function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 5): boolean {
- if (depth >= maxDepth) {
- const identity = getRecursionIdentity(type);
- let count = 0;
- for (let i = 0; i < depth; i++) {
- if (getRecursionIdentity(stack[i]) === identity) {
- count++;
- if (count >= maxDepth) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- // 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 all object types that have an associated symbol (representing the origin of the type), but
- // exclude the static side of classes from this check since it shares its symbol with the instance side.
- return type.symbol;
- }
- if (isTupleType(type)) {
- // Tuple types are tracked through their target type
- return type.target;
- }
- }
- if (type.flags & TypeFlags.TypeParameter) {
- return type.symbol;
- }
- if (type.flags & TypeFlags.IndexedAccess) {
- // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P][Q] 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) {
- const baseType = getBaseTypeOfLiteralType(t);
- if (!commonBaseType) {
- commonBaseType = baseType;
- }
- if (baseType === t || baseType !== commonBaseType) {
- return false;
- }
- }
- return true;
- }
-
- // 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.
- function getSupertypeOrUnion(types: Type[]): Type {
- if (types.length === 1) {
- return types[0];
- }
- return literalTypesWithSameBaseType(types) ?
- getUnionType(types) :
- reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!;
- }
-
- function getCommonSupertype(types: Type[]): Type {
- if (!strictNullChecks) {
- return getSupertypeOrUnion(types);
- }
- const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable));
- return primaryTypes.length ?
- getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) :
- getUnionType(types, UnionReduction.Subtype);
- }
-
- // 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 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 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 {
- return isTupleType(type) || !!getPropertyOfType(type, "0" as __String);
- }
-
- 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 mapType(type, t => getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType);
- }
- 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 {
- return type.flags & TypeFlags.Intersection ? some((type as IntersectionType).types, isUnitType) :
- !!(type.flags & TypeFlags.Unit);
- }
-
- 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.EnumLiteral ? getBaseTypeOfEnumLiteralType(type as LiteralType) :
- type.flags & TypeFlags.StringLiteral ? stringType :
- type.flags & TypeFlags.NumberLiteral ? numberType :
- type.flags & TypeFlags.BigIntLiteral ? bigintType :
- type.flags & TypeFlags.BooleanLiteral ? booleanType :
- type.flags & TypeFlags.Union ? mapType(type as UnionType, getBaseTypeOfLiteralType) :
- type;
- }
-
- function getWidenedLiteralType(type: Type): Type {
- return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(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 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 getRestArrayTypeOfTupleType(type: TupleTypeReference) {
- const restType = getRestTypeOfTupleType(type);
- return restType && createArrayType(restType);
- }
-
- function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = 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);
- }
- 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 getFalsyFlagsOfTypes(types: Type[]): TypeFlags {
- let result: TypeFlags = 0;
- for (const t of types) {
- result |= getFalsyFlags(t);
- }
- return result;
- }
-
- // Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null
- // flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns
- // no flags for all other types (including non-falsy literal types).
- function getFalsyFlags(type: Type): TypeFlags {
- return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((type as UnionType).types) :
- type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value === "" ? TypeFlags.StringLiteral : 0 :
- type.flags & TypeFlags.NumberLiteral ? (type as NumberLiteralType).value === 0 ? TypeFlags.NumberLiteral : 0 :
- type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt(type as BigIntLiteralType) ? TypeFlags.BigIntLiteral : 0 :
- type.flags & TypeFlags.BooleanLiteral ? (type === falseType || type === regularFalseType) ? TypeFlags.BooleanLiteral : 0 :
- type.flags & TypeFlags.PossiblyFalsy;
- }
-
- function removeDefinitelyFalsyTypes(type: Type): Type {
- return getFalsyFlags(type) & TypeFlags.DefinitelyFalsy ?
- filterType(type, t => !(getFalsyFlags(t) & TypeFlags.DefinitelyFalsy)) :
- type;
- }
-
- 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);
- return type.flags & TypeFlags.Undefined ? type : getUnionType([type, isProperty ? missingType : undefinedType]);
- }
-
- function getGlobalNonNullableTypeInstantiation(type: Type) {
- // First reduce away any constituents that are assignable to 'undefined' or 'null'. This not only eliminates
- // 'undefined' and 'null', but also higher-order types such as a type parameter 'U extends undefined | null'
- // that isn't eliminated by a NonNullable instantiation.
- const reducedType = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
- if (!deferredGlobalNonNullableTypeAlias) {
- deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol;
- }
- // If the NonNullable type is available, return an instantiation. Otherwise just return the reduced type.
- return deferredGlobalNonNullableTypeAlias !== unknownSymbol ?
- getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [reducedType]) :
- reducedType;
- }
-
- function getNonNullableType(type: Type): Type {
- return strictNullChecks ? getGlobalNonNullableTypeInstantiation(type) : 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 exactOptionalPropertyTypes && (type === missingType || type.flags & TypeFlags.Union && containsType((type as UnionType).types, 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 {
- 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 &&
- !typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & 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.type = type;
- symbol.target = source;
- if (source.valueDeclaration) {
- symbol.valueDeclaration = source.valueDeclaration;
- }
- const nameType = getSymbolLinks(source).nameType;
- if (nameType) {
- symbol.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() as UnderscoreEscapedMap;
- 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, missingType);
- 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)));
- 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 (isArrayType(type) || isTupleType(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) {
- if (reportWideningErrorsInType(t)) {
- errorReported = true;
- }
- }
- }
- }
- if (isArrayType(type) || isTupleType(type)) {
- for (const t of getTypeArguments(type)) {
- if (reportWideningErrorsInType(t)) {
- errorReported = true;
- }
- }
- }
- if (isObjectLiteralType(type)) {
- for (const p of getPropertiesOfObjectType(type)) {
- const t = getTypeOfSymbol(p);
- if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) {
- if (!reportWideningErrorsInType(t)) {
- error(p.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) &&
- (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) &&
- param.parent.parameters.indexOf(param) > -1 &&
- (resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) ||
- param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) {
- const newName = "arg" + param.parent.parameters.indexOf(param);
- errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, declarationNameToString(param.name));
- 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.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_because_it_does_not_yield_any_values_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 reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) {
- if (produceDiagnostics && noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType && (!wideningKind || !getContextualSignatureForFunctionLikeDeclaration(declaration as FunctionLikeDeclaration))) {
- // 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), targetRestType);
- }
- }
-
- function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) {
- const sourceTypePredicate = getTypePredicateOfSignature(source);
- const targetTypePredicate = getTypePredicateOfSignature(target);
- if (sourceTypePredicate && targetTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) {
- callback(sourceTypePredicate.type, targetTypePredicate.type);
- }
- else {
- callback(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
- }
- }
-
- 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: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ true)),
- nonFixingMapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ false)),
- };
- return context;
- }
-
- function mapToInferredType(context: InferenceContext, t: Type, fix: boolean): Type {
- const inferences = context.inferences;
- for (let i = 0; i < inferences.length; i++) {
- const inference = inferences[i];
- if (t === inference.typeParameter) {
- if (fix && !inference.isFixed) {
- clearCachedInferences(inferences);
- inference.isFixed = true;
- }
- return getInferredType(context, i);
- }
- }
- return t;
- }
-
- function clearCachedInferences(inferences: InferenceInfo[]) {
- for (const inference of inferences) {
- if (!inference.isFixed) {
- inference.inferredType = 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 || forEach(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)) ||
- 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, typeParameter: TypeParameter): boolean {
- return !!(type === typeParameter ||
- type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, typeParameter)) ||
- type.flags & TypeFlags.Conditional && (getTrueTypeFromConditionalType(type as ConditionalType) === typeParameter || getFalseTypeFromConditionalType(type as ConditionalType) === 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.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(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 {
- if (inInferTypeForHomomorphicMappedType) {
- return undefined;
- }
- const key = source.id + "," + target.id + "," + constraint.id;
- if (reverseMappedCache.has(key)) {
- return reverseMappedCache.get(key);
- }
- inInferTypeForHomomorphicMappedType = true;
- const type = createReverseMappedType(source, target, constraint);
- inInferTypeForHomomorphicMappedType = false;
- reverseMappedCache.set(key, 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(getTypeArguments(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)) {
- return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source));
- }
- if (isTupleType(source)) {
- const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint));
- 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) {
- const links = getSymbolLinks(symbol);
- if (!links.type) {
- links.type = inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType);
- }
- return links.type;
- }
-
- function inferReverseMappedType(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* 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 {
- const result = getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next();
- if (!result.done) return result.value;
- }
-
- function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) {
- return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength ||
- !target.target.hasRestElement && (source.target.hasRestElement || 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);
- }
-
- function isValidBigIntString(s: string): boolean {
- const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false);
- let success = true;
- scanner.setOnError(() => success = false);
- scanner.setText(s + "n");
- let result = scanner.scan();
- if (result === SyntaxKind.MinusToken) {
- result = scanner.scan();
- }
- const flags = scanner.getTokenFlags();
- // validate that
- // * scanning proceeded without error
- // * a bigint can be scanned, and that when it is scanned, it is
- // * the full length of the input string (so the scanner is one character beyond the augmented input length)
- // * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input)
- return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator);
- }
-
- function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean {
- if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) {
- return true;
- }
- if (source.flags & TypeFlags.StringLiteral) {
- const value = (source as StringLiteralType).value;
- return !!(target.flags & TypeFlags.Number && value !== "" && isFinite(+value) ||
- target.flags & TypeFlags.BigInt && value !== "" && isValidBigIntString(value) ||
- target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName);
- }
- 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 isTypeAssignableTo(source, target);
- }
-
- function inferTypesFromTemplateLiteralType(source: Type, target: TemplateLiteralType): Type[] | undefined {
- return source.flags & TypeFlags.StringLiteral ? inferFromLiteralPartsToTemplateLiteral([(source as StringLiteralType).value], emptyArray, target) :
- source.flags & TypeFlags.TemplateLiteral ?
- arraysEqual((source as TemplateLiteralType).texts, target.texts) ? map((source as TemplateLiteralType).types, getStringLikeTypeForType) :
- inferFromLiteralPartsToTemplateLiteral((source as TemplateLiteralType).texts, (source as TemplateLiteralType).types, target) :
- undefined;
- }
-
- 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 = 0, contravariant = false) {
- let bivariant = false;
- let propagationType: Type;
- let inferencePriority = InferencePriority.MaxValue;
- let allowComplexConstraintInference = true;
- let visited: ESMap;
- let sourceStack: object[];
- let targetStack: object[];
- let expandingFlags = ExpandingFlags.None;
- inferFromTypes(originalSource, originalTarget);
-
- function inferFromTypes(source: Type, target: Type): void {
- if (!couldContainTypeVariables(target)) {
- return;
- }
- if (source === wildcardType) {
- // 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.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
- // Source and target are types originating in the same generic type alias declaration.
- // Simply infer from source type arguments to target type arguments.
- inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol));
- 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 && some((target as IntersectionType).types,
- t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) {
- // We reduce intersection types only when they contain naked type parameters. 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.
- // Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable"
- // in such scenarios.
- 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);
- }
- }
- else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
- target = getActualTypeVariable(target);
- }
- if (target.flags & TypeFlags.TypeVariable) {
- // If target is a type parameter, make an inference, unless the source type contains
- // the anyFunctionType (the wildcard type that's used to avoid contextually typing functions).
- // Because the anyFunctionType is internal, it should not be exposed to the user by adding
- // it as an inference candidate. Hopefully, a better candidate will come along that does
- // not contain anyFunctionType when we come back to this argument for its second round
- // of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard
- // when constructing types from type parameters that had no inference candidates).
- if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType || source === silentNeverType ||
- (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType)) || isFromInferenceBlockedSource(source)) {
- return;
- }
- const inference = getInferenceInfoForType(target);
- if (inference) {
- if (!inference.isFixed) {
- if (inference.priority === undefined || priority < inference.priority) {
- inference.candidates = undefined;
- inference.contraCandidates = undefined;
- inference.topLevel = true;
- inference.priority = priority;
- }
- if (priority === inference.priority) {
- const candidate = propagationType || source;
- // 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;
- }
- else {
- // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
- const simplified = getSimplifiedType(target, /*writing*/ false);
- if (simplified !== target) {
- invokeOnce(source, simplified, inferFromTypes);
- }
- 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) {
- invokeOnce(source, simplified, inferFromTypes);
- }
- }
- }
- }
- }
- 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) {
- contravariant = !contravariant;
- inferFromTypes((source as IndexType).type, (target as IndexType).type);
- contravariant = !contravariant;
- }
- else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) {
- const empty = createEmptyObjectTypeFromStringLiteral(source);
- contravariant = !contravariant;
- inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof);
- contravariant = !contravariant;
- }
- 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);
- const oldPriority = priority;
- priority |= InferencePriority.SubstituteSource;
- inferFromTypes((source as SubstitutionType).substitute, target); // Make substitute inference at a lower priority
- priority = oldPriority;
- }
- else if (target.flags & TypeFlags.Conditional) {
- invokeOnce(source, target, 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 (!(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 && allowComplexConstraintInference && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) {
- // TODO: The `allowComplexConstraintInference` flag is a hack! This forbids inference from complex constraints within constraints!
- // This isn't required algorithmically, but rather is used to lower the memory burden caused by performing inference
- // that is _too good_ in projects with complicated constraints (eg, fp-ts). In such cases, if we did not limit ourselves
- // here, we might produce more valid inferences for types, causing us to do more checks and perform more instantiations
- // (in addition to the extra stack depth here) which, in turn, can push the already close process over its limit.
- // TL;DR: If we ever become generally more memory efficient (or our resource budget ever increases), we should just
- // remove this `allowComplexConstraintInference` flag.
- allowComplexConstraintInference = false;
- 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 invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => 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;
- const sourceIdentity = getRecursionIdentity(source);
- const targetIdentity = getRecursionIdentity(target);
- if (contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source;
- if (contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target;
- if (expandingFlags !== ExpandingFlags.Both) {
- (sourceStack || (sourceStack = [])).push(sourceIdentity);
- (targetStack || (targetStack = [])).push(targetIdentity);
- action(source, target);
- targetStack.pop();
- sourceStack.pop();
- }
- else {
- inferencePriority = InferencePriority.Circularity;
- }
- 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) {
- if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) {
- contravariant = !contravariant;
- inferFromTypes(source, target);
- contravariant = !contravariant;
- }
- 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) {
- let result = false;
- for (const type of (constraintType as UnionType).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), 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 savePriority = priority;
- priority |= contravariant ? InferencePriority.ContravariantConditional : 0;
- const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)];
- inferToMultipleTypes(source, targetTypes, target.flags);
- priority = savePriority;
- }
- }
-
- 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++) {
- inferFromTypes(matches ? matches[i] : neverType, types[i]);
- }
- }
- }
-
- 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)) {
- // 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);
- }
- 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 (isArrayType(source) || isTupleType(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.hasRestElement ? 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 && elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic && isTupleType(source)) {
- // 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 (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 = isTupleType(source) ? sliceTupleType(source, startLength, endLength) : createArrayType(getTypeArguments(source)[0]);
- 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 = isTupleType(source) ? getElementTypeOfSliceOfTupleType(source, startLength, endLength) : getTypeArguments(source)[0];
- 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) {
- inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp));
- }
- }
- }
-
- function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) {
- const sourceSignatures = getSignaturesOfType(source, kind);
- const targetSignatures = getSignaturesOfType(target, kind);
- const sourceLen = sourceSignatures.length;
- const targetLen = targetSignatures.length;
- const len = sourceLen < targetLen ? sourceLen : targetLen;
- const skipParameters = !!(getObjectFlags(source) & ObjectFlags.NonInferrableType);
- for (let i = 0; i < len; i++) {
- inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getErasedSignature(targetSignatures[targetLen - len + i]), skipParameters);
- }
- }
-
- function inferFromSignature(source: Signature, target: Signature, skipParameters: boolean) {
- if (!skipParameters) {
- 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, inferFromContravariantTypes);
- 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 exactOptionalPropertyTypes && 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);
- const widenLiteralTypes = !primitiveConstraint && inference.topLevel &&
- (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(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;
- const signature = context.signature;
- if (signature) {
- const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined;
- if (inference.contraCandidates) {
- const inferredContravariantType = getContravariantInference(inference);
- // If we have both co- and contra-variant inferences, we prefer the contra-variant inference
- // unless the co-variant inference is a subtype and not 'never'.
- inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
- isTypeSubtypeOf(inferredCovariantType, inferredContravariantType) ?
- inferredCovariantType : inferredContravariantType;
- }
- else if (inferredCovariantType) {
- inferredType = 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))) {
- inference.inferredType = inferredType = instantiatedConstraint;
- }
- }
- }
-
- 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 "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;
- 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.escapedText,
- SymbolFlags.Value | SymbolFlags.ExportValue,
- getCannotFindNameDiagnosticForName(node),
- node,
- !isWriteOnlyAccess(node),
- /*excludeGlobals*/ false) || unknownSymbol;
- }
- return links.resolvedSymbol;
- }
-
- function isInTypeQuery(node: Node): boolean {
- // TypeScript 1.0 spec (April 2014): 3.6.3
- // A type query consists of the keyword typeof followed by an expression.
- // The expression is restricted to a single identifier or a sequence of identifiers separated by periods
- return !!findAncestor(
- node,
- n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit");
- }
-
- // 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:
- const symbol = getResolvedSymbol(node as Identifier);
- return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${getSymbolId(symbol)}` : undefined;
- 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;
- }
- }
- 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) ||
- (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) &&
- getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target);
- case SyntaxKind.ThisKeyword:
- return target.kind === SyntaxKind.ThisKeyword;
- case SyntaxKind.SuperKeyword:
- return target.kind === SyntaxKind.SuperKeyword;
- case SyntaxKind.NonNullExpression:
- case SyntaxKind.ParenthesizedExpression:
- return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target);
- case SyntaxKind.PropertyAccessExpression:
- case SyntaxKind.ElementAccessExpression:
- return isAccessExpression(target) &&
- getAccessedPropertyName(source as AccessExpression) === getAccessedPropertyName(target) &&
- isMatchingReference((source as AccessExpression).expression, target.expression);
- 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 getPropertyAccess(expr: Expression) {
- if (isAccessExpression(expr)) {
- return expr;
- }
- if (isIdentifier(expr)) {
- const symbol = getResolvedSymbol(expr);
- if (isConstVariable(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)) {
- 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))) {
- return declaration;
- }
- }
- }
- }
- return undefined;
- }
-
- function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined {
- let propertyName;
- return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
- access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
- access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) :
- 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) {
- if ((prop as TransientSymbol).isDiscriminantProperty === undefined) {
- (prop as TransientSymbol).isDiscriminantProperty =
- ((prop as TransientSymbol).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant &&
- !isGenericType(getTypeOfSymbol(prop));
- }
- return !!(prop as TransientSymbol).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)) {
- 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 || 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) {
- if (assignedType.flags & TypeFlags.Never) {
- return assignedType;
- }
- let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
- if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) {
- reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types
- }
- // 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.)
- if (isTypeAssignableTo(assignedType, reducedType)) {
- return reducedType;
- }
- }
- return declaredType;
- }
-
- function isFunctionObjectType(type: ObjectType): boolean {
- // 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, ignoreObjects = false): TypeFacts {
- const flags = type.flags;
- if (flags & TypeFlags.String) {
- return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts;
- }
- if (flags & TypeFlags.StringLiteral) {
- const isEmpty = (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 && !ignoreObjects) {
- 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 | 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.Instantiable) {
- return !isPatternLiteralType(type) ? getTypeFacts(getBaseConstraintOfType(type) || unknownType, ignoreObjects) :
- strictNullChecks ? TypeFacts.NonEmptyStringStrictFacts : TypeFacts.NonEmptyStringFacts;
- }
- if (flags & TypeFlags.Union) {
- return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFacts(t, ignoreObjects), TypeFacts.None);
- }
- if (flags & TypeFlags.Intersection) {
- // 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.
- ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive);
- return reduceLeft((type as UnionType).types, (facts, t) => facts & getTypeFacts(t, ignoreObjects), TypeFacts.All);
- }
- return TypeFacts.All;
- }
-
- function getTypeWithFacts(type: Type, include: TypeFacts) {
- return filterType(type, t => (getTypeFacts(t) & include) !== 0);
- }
-
- 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, undefinedType]) :
- 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 types from all cases in a switch on `typeof`. An
- // `undefined` element denotes an explicit `default` clause.
- function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: false): string[];
- function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[];
- function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[] {
- const witnesses: (string | undefined)[] = [];
- for (const clause of switchStatement.caseBlock.clauses) {
- if (clause.kind === SyntaxKind.CaseClause) {
- if (isStringLiteralLike(clause.expression)) {
- witnesses.push(clause.expression.text);
- continue;
- }
- return emptyArray;
- }
- if (retainDefault) witnesses.push(/*explicitDefaultStatement*/ 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 || 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.EnumLiteral && getBaseTypeOfEnumLiteralType(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);
- }
- }
- return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags, /*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 getConstituentCount(type: Type) {
- return type.flags & TypeFlags.UnionOrIntersection ? (type as UnionOrIntersectionType).types.length : 1;
- }
-
- 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 as IncompleteType).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) {
- 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).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.flags & SymbolFlags.Alias ? resolveAlias(symbol) : 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) {
- 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 (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).antecedents, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false));
- }
- else if (flags & FlowFlags.LoopLabel) {
- const antecedents = (flow as FlowLabel).antecedents;
- 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.
- if ((flow as FlowSwitchClause).clauseStart === (flow as FlowSwitchClause).clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).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).target;
- const saveAntecedents = target.antecedents;
- target.antecedents = (flow as FlowReduceLabel).antecedents;
- const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false);
- target.antecedents = 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).antecedents, 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).antecedents![0];
- }
- else if (flags & FlowFlags.ReduceLabel) {
- const target = (flow as FlowReduceLabel).target;
- const saveAntecedents = target.antecedents;
- target.antecedents = (flow as FlowReduceLabel).antecedents;
- const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false);
- target.antecedents = 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.Identifier: {
- const symbol = getResolvedSymbol(node as Identifier);
- return isConstVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol);
- }
- 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);
- }
- return false;
- }
-
- function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node) {
- let key: string | undefined;
- let isKeySet = false;
- let flowDepth = 0;
- if (flowAnalysisDisabled) {
- return errorType;
- }
- if (!reference.flowNode) {
- return declaredType;
- }
- flowInvocationCount++;
- const sharedFlowStart = sharedFlowCount;
- const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.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;
- }
- // The non-null unknown type should never escape control flow analysis.
- return resultType === nonNullUnknownType ? unknownType : 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).antecedents!.length === 1) {
- flow = (flow as FlowLabel).antecedents![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).target;
- const saveAntecedents = target.antecedents;
- target.antecedents = (flow as FlowReduceLabel).antecedents;
- type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent);
- target.antecedents = 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) {
- 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;
- }
- if (declaredType.flags & TypeFlags.Union) {
- return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow));
- }
- return declaredType;
- }
- // 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) || isVarConst(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)) {
- return getNonNullableTypeIfNeeded(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 = flow.switchStatement.expression;
- const flowType = getTypeAtFlowNode(flow.antecedent);
- let type = getTypeFromFlowType(flowType);
- if (isMatchingReference(reference, expr)) {
- type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
- }
- else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
- type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
- }
- else {
- if (strictNullChecks) {
- if (optionalChainContainsReference(expr, reference)) {
- type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd,
- t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never)));
- }
- else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) {
- type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd,
- 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.switchStatement, flow.clauseStart, flow.clauseEnd);
- }
- }
- 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.antecedents!) {
- if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).clauseStart === (antecedent as FlowSwitchClause).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, declaredType)) {
- 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 (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) {
- if (type === declaredType && declaredType === initialType) {
- return type;
- }
- antecedentTypes.push(type);
- if (!isTypeSubsetOf(type, declaredType)) {
- 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.antecedents!) {
- 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, declaredType)) {
- 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 = getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction);
- if (result !== declaredType && result.flags & declaredType.flags & TypeFlags.Union && arraysEqual((result as UnionType).types, (declaredType as UnionType).types)) {
- return declaredType;
- }
- return result;
- }
-
- function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
- let access, name;
- const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType;
- return type.flags & TypeFlags.Union && (access = getPropertyAccess(expr)) && (name = getAccessedPropertyName(access)) &&
- isMatchingReference(reference, isAccessExpression(access) ? access.expression : access.parent.parent.initializer!) &&
- isDiscriminantProperty(type, name) ?
- access : undefined;
- }
-
- function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type {
- const propName = getAccessedPropertyName(access);
- if (propName === undefined) {
- return type;
- }
- const removeNullable = strictNullChecks && isOptionalChain(access) && maybeTypeOfKind(type, TypeFlags.Nullable);
- let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName);
- if (!propType) {
- return type;
- }
- propType = removeNullable ? getOptionalType(propType) : propType;
- const narrowedPropType = narrowType(propType);
- return filterType(type, t => {
- const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName);
- return !(narrowedPropType.flags & TypeFlags.Never) && isTypeComparableTo(narrowedPropType, discriminantType);
- });
- }
-
- function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, 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, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
- if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) {
- const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, 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, switchStatement, clauseStart, clauseEnd));
- }
-
- function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
- if (isMatchingReference(reference, expr)) {
- return type.flags & TypeFlags.Unknown && assumeTrue ? nonNullUnknownType :
- getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
- }
- if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
- type = getTypeWithFacts(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);
- if (prop) {
- return prop.flags & SymbolFlags.Optional ? true : assumeTrue;
- }
- return getApplicableIndexInfoForName(type, propName) ? true : !assumeTrue;
- }
-
- function widenTypeWithSymbol(type: Type, newSymbol: Symbol): Type {
- // If type is this/any/unknown, it could not be widened.
- if ((type.flags & TypeFlags.AnyOrUnknown) || isThisTypeParameter(type)) {
- return type;
- }
- const propName = newSymbol.escapedName;
- const members = createSymbolTable();
- members.set(propName, newSymbol);
- const newObjType = createWidenType(/* symbol */ undefined, members, emptyArray, emptyArray, emptyArray);
-
- // if `type` is never, just return the new anonymous object type.
- if (type.flags & TypeFlags.Never) {
- return newObjType;
- }
-
- // if type is intersection, we might have added type into it, and we just need to add into this type again rather than a new one.
- // else add a new anonymous object type which contains the type and widen the original type with it.
-
- if (isIntersectionType(type)) {
- // try to get the first Anonymous Object type to add new type to it.
- const widenedType: Type | undefined = type.types.find(t => isObjectType(t) && t.objectFlags & ObjectFlags.WidenedByNarrow);
- if (widenedType && isObjectType(widenedType)) {
- const typeWithOutWiden = filterIntersectionType(type, t => t !== widenedType);
-
- const members = createSymbolTable();
- members.set(propName, newSymbol);
- if (widenedType.members) {
- mergeSymbolTable(members, widenedType.members);
- }
- newObjType.members = members;
- newObjType.properties = getNamedMembers(members);
- return createIntersectionType([typeWithOutWiden, newObjType]);
- }
- }
- return createIntersectionType([type, newObjType]);
-
- // this function is almost like `filterType`, expect that the `type` is Intersection rather than Union.
- // maybe we should advanced `filterType`, but I do not know whether it would be too far.
- function filterIntersectionType(type: Type, f: (t: Type) => boolean): Type {
- if (type.flags & TypeFlags.Intersection) {
- const types = (type).types;
- const filtered = filter(types, f);
-
- return filtered === types ? type : getIntersectionTypeFromSortedList(filtered, (type).objectFlags);
- }
- return type.flags & TypeFlags.Never || f(type) ? type : neverType;
- }
-
- // I would be very glad to create a helper file like `nodeTests.ts` if feedback positive review.
- function isIntersectionType(type: Type): type is IntersectionType {
- return !!(type.flags & TypeFlags.Intersection);
- }
-
- function isObjectType(type: Type): type is ObjectType {
- return !!(type.flags & TypeFlags.Object);
- }
- }
-
- function narrowOrWidenTypeByInKeyword(type: Type, name: __String, assumeTrue: boolean) {
- if ((type.flags & TypeFlags.Union
- || type.flags & TypeFlags.Object && declaredType !== type
- || isThisTypeParameter(type)
- || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) && isSomeDirectSubtypeContainsPropName(type, name)) {
- return filterType(type, t => isTypePresencePossible(t, name, assumeTrue));
- }
- // only widen property when the type does not contain string-index/name in any of the constituents.
- else if (assumeTrue && !isSomeDirectSubtypeContainsPropName(type, name) && !getIndexInfoOfType(type, stringType)) {
- const addSymbol = createSymbol(SymbolFlags.Property, name);
- addSymbol.type = unknownType;
- return widenTypeWithSymbol(type, addSymbol);
- }
- return type;
-
- // This function is almost like function `getPropertyOfType`, except when type.flags contains `UnionOrIntersection`
- // it would return the property rather than undefiend even when property is partial.
- function isSomeDirectSubtypeContainsPropName(type: Type, name: __String): Symbol | undefined {
- type = getReducedApparentType(type);
- if (type.flags & TypeFlags.Object) {
- const resolved = resolveStructuredTypeMembers(type);
- const symbol = resolved.members.get(name);
- if (symbol && symbolIsValue(symbol)) {
- return symbol;
- }
- const functionType = resolved === anyFunctionType ? globalFunctionType :
- resolved.callSignatures.length ? globalCallableFunctionType :
- resolved.constructSignatures.length ? globalNewableFunctionType :
- undefined;
- if (functionType) {
- const symbol = getPropertyOfObjectType(functionType, name);
- if (symbol) {
- return symbol;
- }
- }
- return getPropertyOfObjectType(globalObjectType, name);
- }
- if (type.flags & TypeFlags.UnionOrIntersection) {
- return getUnionOrIntersectionProperty(type, name);
- }
- return undefined;
- }
- }
-
- 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);
- }
- break;
- case SyntaxKind.InstanceOfKeyword:
- return narrowTypeByInstanceof(type, expr, assumeTrue);
- case SyntaxKind.InKeyword:
- if (isPrivateIdentifier(expr.left)) {
- return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue);
- }
- const target = getReferenceCandidate(expr.right);
- const leftType = getTypeOfNode(expr.left);
- if (leftType.flags & TypeFlags.StringLiteral) {
- const name = escapeLeadingUnderscores((leftType as StringLiteralType).value);
- if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target) &&
- getAccessedPropertyName(reference) === name) {
- return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined);
- }
- if (isMatchingReference(reference, target)) {
- return narrowOrWidenTypeByInKeyword(type, name, 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, isTypeDerivedFrom);
- }
-
- 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 ? getTypeWithFacts(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);
- if (assumeTrue && (type.flags & TypeFlags.Unknown) && (operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken) && (valueType.flags & TypeFlags.Null)) {
- return getUnionType([nullType, undefinedType]);
- }
- if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) {
- if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) {
- return valueType;
- }
- if (valueType.flags & TypeFlags.Object) {
- return nonPrimitiveType;
- }
- return type;
- }
- if (valueType.flags & TypeFlags.Nullable) {
- if (!strictNullChecks) {
- return type;
- }
- const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
- const facts = doubleEquals ?
- assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull :
- valueType.flags & TypeFlags.Null ?
- assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
- assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined;
- return type.flags & TypeFlags.Unknown && facts & (TypeFacts.NENull | TypeFacts.NEUndefinedOrNull) ? nonNullUnknownType : getTypeWithFacts(type, facts);
- }
- if (assumeTrue) {
- const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ?
- t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType) :
- t => areTypesComparable(t, valueType);
- return replacePrimitivesWithLiterals(filterType(type, filterFn), 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")) {
- return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
- }
- return type;
- }
- if (type.flags & TypeFlags.Any && literal.text === "function") {
- return type;
- }
- if (assumeTrue && type.flags & TypeFlags.Unknown && literal.text === "object") {
- // The non-null unknown type is used to track whether a previous narrowing operation has removed the null type
- // from the unknown type. For example, the expression `x && typeof x === 'object'` first narrows x to the non-null
- // unknown type, and then narrows that to the non-primitive type.
- return type === nonNullUnknownType ? nonPrimitiveType : getUnionType([nonPrimitiveType, nullType]);
- }
- const facts = assumeTrue ?
- typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
- typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject;
- const impliedType = getImpliedTypeFromTypeofGuard(type, literal.text);
- return getTypeWithFacts(assumeTrue && impliedType ? mapType(type, narrowUnionMemberByTypeof(impliedType)) : type, facts);
- }
-
- function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, 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: SwitchStatement, clauseStart: number, clauseEnd: number) {
- // 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, getRegularTypeOfLiteralType(extractUnitType(t)))));
- return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
- }
-
- function getImpliedTypeFromTypeofGuard(type: Type, text: string) {
- switch (text) {
- case "function":
- return type.flags & TypeFlags.Any ? type : globalFunctionType;
- case "object":
- return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
- default:
- return typeofTypesByName.get(text);
- }
- }
-
- // When narrowing a union type by a `typeof` guard using type-facts alone, constituent types that are
- // super-types of the implied guard will be retained in the final type: this is because type-facts only
- // filter. Instead, we would like to replace those union constituents with the more precise type implied by
- // the guard. For example: narrowing `{} | undefined` by `"boolean"` should produce the type `boolean`, not
- // the filtered type `{}`. For this reason we narrow constituents of the union individually, in addition to
- // filtering by type-facts.
- function narrowUnionMemberByTypeof(candidate: Type) {
- return (type: Type) => {
- if (isTypeSubtypeOf(type, candidate)) {
- return type;
- }
- if (isTypeSubtypeOf(candidate, type)) {
- return candidate;
- }
- if (type.flags & TypeFlags.Instantiable) {
- const constraint = getBaseConstraintOfType(type) || anyType;
- if (isTypeSubtypeOf(candidate, constraint)) {
- return getIntersectionType([type, candidate]);
- }
- }
- return type;
- };
- }
-
- function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
- const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement, /*retainDefault*/ true);
- if (!switchWitnesses.length) {
- return type;
- }
- // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause
- const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined);
- const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd);
- let clauseWitnesses: string[];
- let switchFacts: TypeFacts;
- if (defaultCaseLocation > -1) {
- // We no longer need the undefined denoting an explicit default case. Remove the undefined and
- // fix-up clauseStart and clauseEnd. This means that we don't have to worry about undefined in the
- // witness array.
- const witnesses = switchWitnesses.filter(witness => witness !== undefined) as string[];
- // The adjusted clause start and end after removing the `default` statement.
- const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
- const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd;
- clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd);
- switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause);
- }
- else {
- clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd) as string[];
- switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses as string[], hasDefaultClause);
- }
- if (hasDefaultClause) {
- return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts);
- }
- /*
- The implied type is the raw type suggested by a
- value being caught in this clause.
-
- When the clause contains a default case we ignore
- the implied type and try to narrow using any facts
- we can learn: see `switchFacts`.
-
- Example:
- switch (typeof x) {
- case 'number':
- case 'string': break;
- default: break;
- case 'number':
- case 'boolean': break
- }
-
- In the first clause (case `number` and `string`) the
- implied type is number | string.
-
- In the default clause we de not compute an implied type.
-
- In the third clause (case `number` and `boolean`)
- the naive implied type is number | boolean, however
- we use the type facts to narrow the implied type to
- boolean. We know that number cannot be selected
- because it is caught in the first clause.
- */
- const impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofGuard(type, text) || type)), switchFacts);
- return getTypeWithFacts(mapType(type, narrowUnionMemberByTypeof(impliedType)), switchFacts);
- }
-
- 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: BinaryExpression, assumeTrue: boolean): Type {
- const left = getReferenceCandidate(expr.left);
- if (!isMatchingReference(reference, left)) {
- if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) {
- return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
- }
- return type;
- }
-
- // Check that right operand is a function type with a prototype property
- const rightType = getTypeOfExpression(expr.right);
- if (!isTypeDerivedFrom(rightType, globalFunctionType)) {
- return type;
- }
-
- let targetType: Type | undefined;
- const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String);
- if (prototypeProperty) {
- // Target type is type of the prototype property
- const prototypePropertyType = getTypeOfSymbol(prototypeProperty);
- if (!isTypeAny(prototypePropertyType)) {
- targetType = prototypePropertyType;
- }
- }
-
- // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function'
- if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) {
- return type;
- }
-
- if (!targetType) {
- const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
- targetType = constructSignatures.length ?
- getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) :
- emptyObjectType;
- }
-
- // We can't narrow a union based off instanceof without negated types see #31576 for more info
- if (!assumeTrue && rightType.flags & TypeFlags.Union) {
- const nonConstructorTypeInUnion = find((rightType as UnionType).types, (t) => !isConstructorType(t));
- if (!nonConstructorTypeInUnion) return type;
- }
-
- return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
- }
-
- function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) {
- if (!assumeTrue) {
- return filterType(type, t => !isRelated(t, candidate));
- }
- // If the current type is a union type, remove all constituents that couldn't be instances of
- // the candidate type. If one or more constituents remain, return a union of those.
- if (type.flags & TypeFlags.Union) {
- const assignableType = filterType(type, t => isRelated(t, candidate));
- if (!(assignableType.flags & TypeFlags.Never)) {
- return assignableType;
- }
- }
-
- // If the candidate type is a subtype of the target type, narrow to the candidate type.
- // Otherwise, if the target type is assignable to the candidate type, keep the target type.
- // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
- // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
- // two types.
- return 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, isTypeSubtypeOf);
- }
- if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) &&
- !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) {
- type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
- }
- const access = getDiscriminantPropertyAccess(predicateArgument, type);
- if (access) {
- return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf));
- }
- }
- }
- 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.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 (isConstVariable(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:
- return narrowType(type, (expr as ParenthesizedExpression | NonNullExpression).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 getTypeWithFacts(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 = symbol.exportSymbol || 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 = getTypeOfExpression(location as Expression);
- if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) {
- return type;
- }
- }
- }
- if (isDeclarationName(location) && isSetAccessor(location.parent) && getAnnotatedAccessorTypeNode(location.parent)) {
- return resolveTypeOfAccessors(location.parent.symbol, /*writing*/ true)!;
- }
- // 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 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 or catch variable is assigned anywhere
- function isSymbolAssigned(symbol: Symbol) {
- if (!symbol.valueDeclaration) {
- return false;
- }
- const parent = getRootDeclaration(symbol.valueDeclaration).parent;
- const links = getNodeLinks(parent);
- if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) {
- links.flags |= NodeCheckFlags.AssignmentsMarked;
- if (!hasParentWithAssignmentsMarked(parent)) {
- markNodeAssignments(parent);
- }
- }
- return symbol.isAssigned || false;
- }
-
- function hasParentWithAssignmentsMarked(node: Node) {
- return !!findAncestor(node.parent, node =>
- (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked));
- }
-
- function markNodeAssignments(node: Node) {
- if (node.kind === SyntaxKind.Identifier) {
- if (isAssignmentTarget(node)) {
- const symbol = getResolvedSymbol(node as Identifier);
- if (isParameterOrCatchClauseVariable(symbol)) {
- symbol.isAssigned = true;
- }
- }
- }
- else {
- forEachChild(node, markNodeAssignments);
- }
- }
-
- function isConstVariable(symbol: Symbol) {
- return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0;
- }
-
- /** 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 {
- if (pushTypeResolution(declaration.symbol, TypeSystemPropertyName.DeclaredType)) {
- const annotationIncludesUndefined = strictNullChecks &&
- declaration.kind === SyntaxKind.Parameter &&
- declaration.initializer &&
- getFalsyFlags(declaredType) & TypeFlags.Undefined &&
- !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined);
- popTypeResolution();
-
- return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType;
- }
- else {
- reportCircularityError(declaration.symbol);
- return 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.CallExpression && (parent as CallExpression).expression === node ||
- parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node &&
- !(isGenericTypeWithoutNullableConstraint(type) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression)));
- }
-
- function isGenericTypeWithUnionConstraint(type: Type) {
- return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union));
- }
-
- function isGenericTypeWithoutNullableConstraint(type: Type) {
- return !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable));
- }
-
- function hasNonBindingPatternContextualTypeWithNoGenericTypes(node: Node) {
- // 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.
- const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) &&
- !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) &&
- getContextualType(node, ContextFlags.SkipBindingPatterns);
- return contextualType && !isGenericType(contextualType);
- }
-
- function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) {
- // 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) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference));
- return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : 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;
- });
- }
-
- function markAliasReferenced(symbol: Symbol, location: Node) {
- if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !getTypeOnlyAliasDeclaration(symbol)) {
- const target = resolveAlias(symbol);
- if (target.flags & SymbolFlags.Value) {
- // 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 (compilerOptions.isolatedModules ||
- shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) ||
- !isConstEnumOrConstEnumOnlyModule(target)
- ) {
- markAliasSymbolAsReferenced(symbol);
- }
- else {
- markConstEnumAliasAsReferenced(symbol);
- }
- }
- }
- }
-
- function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type {
- const symbol = getResolvedSymbol(node);
- if (symbol === unknownSymbol) {
- return errorType;
- }
-
- // 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)) {
- error(node, Diagnostics.arguments_cannot_be_referenced_in_property_initializers);
- return errorType;
- }
-
- const container = getContainingFunction(node)!;
- if (languageVersion < ScriptTarget.ES2015) {
- if (container.kind === SyntaxKind.ArrowFunction) {
- error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_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_ES3_and_ES5_Consider_using_a_standard_function_or_method);
- }
- }
-
- getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments;
- return getTypeOfSymbol(symbol);
- }
-
- // We should only mark aliases as referenced if there isn't a local value declaration
- // for the symbol. Also, don't mark any property access expression LHS - checkPropertyAccessExpression will handle that
- if (!(node.parent && isPropertyAccessExpression(node.parent) && node.parent.expression === node)) {
- markAliasReferenced(symbol, node);
- }
-
- const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
- const sourceSymbol = localOrExportSymbol.flags & SymbolFlags.Alias ? resolveAlias(localOrExportSymbol) : localOrExportSymbol;
- if (sourceSymbol.declarations && getDeclarationNodeFlagsFromSymbol(sourceSymbol) & NodeFlags.Deprecated && isUncalledFunctionReference(node, sourceSymbol)) {
- addDeprecatedSuggestion(node, sourceSymbol.declarations, node.escapedText as string);
- }
-
- let declaration = localOrExportSymbol.valueDeclaration;
- if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) {
- // Due to the emit for class decorators, any reference to the class from inside of the class body
- // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
- // behavior of class names in ES6.
- if (declaration.kind === SyntaxKind.ClassDeclaration
- && nodeIsDecorated(declaration as ClassDeclaration)) {
- let container = getContainingClass(node);
- while (container !== undefined) {
- if (container === declaration && container.name !== node) {
- getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference;
- getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass;
- break;
- }
-
- container = getContainingClass(container);
- }
- }
- else if (declaration.kind === SyntaxKind.ClassExpression) {
- // When we emit a class expression with static members that contain a reference
- // to the constructor in the initializer, we will need to substitute that
- // binding with an alias as the class name is not in scope.
- let container = getThisContainer(node, /*includeArrowFunctions*/ false);
- while (container.kind !== SyntaxKind.SourceFile) {
- if (container.parent === declaration) {
- if (isPropertyDeclaration(container) && isStatic(container) || isClassStaticBlockDeclaration(container)) {
- getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference;
- getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass;
- }
- break;
- }
-
- container = getThisContainer(container, /*includeArrowFunctions*/ false);
- }
- }
- }
-
- checkNestedBlockScopedBinding(node, symbol);
-
- let type = getTypeOfSymbol(localOrExportSymbol);
- 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 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;
- // When the control flow originates in a function expression or arrow function and we are referencing
- // a const variable or parameter from an outer function, we extend the origin of the control flow
- // analysis to include the immediately enclosing function.
- while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression ||
- flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) &&
- (isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))) {
- 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 assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isBindingElement(declaration) ||
- type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 ||
- isInTypeQuery(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 = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) :
- type === autoType || type === autoArrayType ? undefinedType :
- getOptionalType(type);
- const flowType = 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 && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
- 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 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, getSymbolOfNode(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: ClassDeclaration): boolean {
- const classSymbol = getSymbolOfNode(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 (node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) {
- error(node, diagnosticMessage);
- }
- }
- }
-
- function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) {
- if (isPropertyDeclaration(container) && hasStaticModifier(container) &&
- container.initializer && textRangeContainsPositionInclusive(container.initializer, thisExpression.pos) && length(container.parent.decorators)) {
- 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);
- let capturedByArrowFunction = false;
-
- if (container.kind === SyntaxKind.Constructor) {
- checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class);
- }
-
- // Now skip arrow functions to get the "real" owner of 'this'.
- if (container.kind === SyntaxKind.ArrowFunction) {
- container = getThisContainer(container, /* includeArrowFunctions */ false);
- capturedByArrowFunction = true;
- }
-
- checkThisInStaticClassFieldInitializerInDecoratedClass(node, container);
- 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;
- case SyntaxKind.Constructor:
- if (isInConstructorArgumentInitializer(node, container)) {
- error(node, Diagnostics.this_cannot_be_referenced_in_constructor_arguments);
- // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks
- }
- break;
- case SyntaxKind.ComputedPropertyName:
- error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name);
- 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)): 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 = getSymbolOfNode(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 = getSymbolOfNode(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);
- if (isFunctionLike(container)) {
- const signature = getSignatureFromDeclaration(container);
- if (signature.thisParameter) {
- return getExplicitTypeOfSymbol(signature.thisParameter);
- }
- }
- if (isClassLike(container.parent)) {
- const symbol = getSymbolOfNode(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: Node) {
- const jsdocType = getJSDocType(node);
- if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) {
- const jsDocFunctionType = jsdocType as JSDocFunctionType;
- if (jsDocFunctionType.parameters.length > 0 &&
- jsDocFunctionType.parameters[0].name &&
- (jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) {
- return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!);
- }
- }
- const thisTag = getJSDocThisTag(node);
- if (thisTag && thisTag.typeExpression) {
- return getTypeFromTypeNode(thisTag.typeExpression);
- }
- }
-
- 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;
-
- // 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) {
- container = getSuperContainer(container, /*stopOnFunctions*/ true);
- needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015;
- }
- }
-
- const canUseSuperExpression = isLegalUsageOfSuperExpression(container);
- let nodeCheckFlag: NodeCheckFlags = 0;
-
- if (!canUseSuperExpression) {
- // 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 && hasSyntacticModifier(container, ModifierFlags.Async)) {
- if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) {
- getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding;
- }
- else {
- getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper;
- }
- }
-
- 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;
- }
-
- const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(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 (!container) {
- return false;
- }
-
- 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 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);
- let literal = containingLiteral;
- let type = contextualType;
- while (type) {
- const thisType = getThisTypeFromContextualType(type);
- if (thisType) {
- return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral)));
- }
- if (literal.parent.kind !== SyntaxKind.PropertyAssignment) {
- break;
- }
- literal = literal.parent.parent as ObjectLiteralExpression;
- type = getApparentTypeOfContextualType(literal);
- }
- // 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 (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) {
- const target = (parent as BinaryExpression).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): Type | undefined {
- const typeNode = getEffectiveTypeAnnotationNode(declaration);
- if (typeNode) {
- return getTypeFromTypeNode(typeNode);
- }
- switch (declaration.kind) {
- case SyntaxKind.Parameter:
- return getContextuallyTypedParameterType(declaration);
- case SyntaxKind.BindingElement:
- return getContextualTypeForBindingElement(declaration);
- case SyntaxKind.PropertyDeclaration:
- if (isStatic(declaration)) {
- return getContextualTypeForStaticPropertyDeclaration(declaration);
- }
- // By default, do nothing and return undefined - only the above cases have context implied by a parent
- }
- }
-
- function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined {
- const parent = declaration.parent.parent;
- const name = declaration.propertyName || declaration.name;
- const parentType = getContextualTypeForVariableLikeDeclaration(parent) ||
- parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent);
- 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): Type | undefined {
- const parentType = isExpression(declaration.parent) && getContextualType(declaration.parent);
- if (!parentType) return undefined;
- return getTypeOfPropertyOfContextualType(parentType, getSymbolOfNode(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): Type | undefined {
- const declaration = node.parent as VariableLikeDeclaration;
- if (hasInitializer(declaration) && node === declaration.initializer) {
- const result = getContextualTypeForVariableLikeDeclaration(declaration);
- if (result) {
- return result;
- }
- if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable
- return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false);
- }
- }
- return undefined;
- }
-
- function getContextualTypeForReturnExpression(node: Expression): Type | undefined {
- const func = getContainingFunction(node);
- if (func) {
- let contextualReturnType = getContextualReturnType(func);
- if (contextualReturnType) {
- const functionFlags = getFunctionFlags(func);
- if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function
- const use = functionFlags & FunctionFlags.Async ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType;
- const iterationTypes = getIterationTypesOfIterable(contextualReturnType, use, /*errorNode*/ undefined);
- if (!iterationTypes) {
- return undefined;
- }
- contextualReturnType = iterationTypes.returnType;
- // falls through to unwrap Promise for AsyncGenerators
- }
-
- if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function
- // Get the awaited type without the `Awaited` alias
- const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias);
- return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
- }
-
- return contextualReturnType; // Regular function or Generator function
- }
- }
- return undefined;
- }
-
- function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags?: ContextFlags): Type | undefined {
- const contextualType = getContextualType(node, contextFlags);
- if (contextualType) {
- const contextualAwaitedType = getAwaitedTypeNoAlias(contextualType);
- return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
- }
- return undefined;
- }
-
- function getContextualTypeForYieldOperand(node: YieldExpression): Type | undefined {
- const func = getContainingFunction(node);
- if (func) {
- const functionFlags = getFunctionFlags(func);
- const contextualReturnType = getContextualReturnType(func);
- if (contextualReturnType) {
- return node.asteriskToken
- ? contextualReturnType
- : getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0);
- }
- }
-
- return undefined;
- }
-
- function isInParameterInitializerBeforeContainingFunction(node: Node) {
- let inBindingInitializer = false;
- while (node.parent && !isFunctionLike(node.parent)) {
- if (isParameter(node.parent) && (inBindingInitializer || node.parent.initializer === node)) {
- return true;
- }
- if (isBindingElement(node.parent) && node.parent.initializer === node) {
- inBindingInitializer = true;
- }
-
- node = node.parent;
- }
-
- return false;
- }
-
- function getContextualIterationType(kind: IterationTypeKind, functionDecl: SignatureDeclaration): Type | undefined {
- const isAsync = !!(getFunctionFlags(functionDecl) & FunctionFlags.Async);
- const contextualReturnType = getContextualReturnType(functionDecl);
- if (contextualReturnType) {
- return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync)
- || undefined;
- }
-
- return undefined;
- }
-
- function getContextualReturnType(functionDecl: SignatureDeclaration): Type | undefined {
- // If the containing function has a return type annotation, is a constructor, or is a get accessor whose
- // corresponding set accessor has a type annotation, return statements in the function are contextually typed
- const returnType = getReturnTypeFromAnnotation(functionDecl);
- if (returnType) {
- return returnType;
- }
- // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature
- // and that call signature is non-generic, return statements are contextually typed by the return type of the signature
- const signature = getContextualSignatureForFunctionLikeDeclaration(functionDecl as FunctionExpression);
- if (signature && !isResolvingReturnTypeOfSignature(signature)) {
- return getReturnTypeOfSignature(signature);
- }
- const iife = getImmediatelyInvokedFunctionExpression(functionDecl);
- if (iife) {
- return getContextualType(iife);
- }
- return undefined;
- }
-
- // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter.
- function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type | undefined {
- const args = getEffectiveCallArguments(callTarget);
- const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression
- return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex);
- }
-
- function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type {
- if (isImportCall(callTarget)) {
- return argIndex === 0 ? stringType :
- argIndex === 1 ? getGlobalImportCallOptionsType(/*reportErrors*/ false) :
- anyType;
- }
-
- // If we're already in the process of resolving the given signature, don't resolve again as
- // that could cause infinite recursion. Instead, return anySignature.
- const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
-
- if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) {
- return getEffectiveFirstArgumentForJsxSignature(signature, callTarget);
- }
- const restIndex = signature.parameters.length - 1;
- return signatureHasRestParameter(signature) && argIndex >= restIndex ?
- getIndexedAccessType(getTypeOfSymbol(signature.parameters[restIndex]), getNumberLiteralType(argIndex - restIndex), AccessFlags.Contextual) :
- getTypeAtPosition(signature, argIndex);
- }
-
- function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) {
- if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) {
- return getContextualTypeForArgument(template.parent as TaggedTemplateExpression, substitutionExpression);
- }
-
- return undefined;
- }
-
- function getContextualTypeForBinaryOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined {
- const binaryExpression = node.parent as BinaryExpression;
- const { left, operatorToken, right } = binaryExpression;
- switch (operatorToken.kind) {
- case SyntaxKind.EqualsToken:
- case SyntaxKind.AmpersandAmpersandEqualsToken:
- case SyntaxKind.BarBarEqualsToken:
- case SyntaxKind.QuestionQuestionEqualsToken:
- return node === right ? getContextualTypeForAssignmentDeclaration(binaryExpression) : undefined;
- case SyntaxKind.BarBarToken:
- case SyntaxKind.QuestionQuestionToken:
- // When an || expression has a contextual type, the operands are contextually typed by that type, except
- // when that type originates in a binding pattern, the right operand is contextually typed by the type of
- // the left operand. When an || expression has no contextual type, the right operand is contextually typed
- // by the type of the left operand, except for the special case of Javascript declarations of the form
- // `namespace.prop = namespace.prop || {}`.
- const type = getContextualType(binaryExpression, contextFlags);
- return node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ?
- getTypeOfExpression(left) : type;
- case SyntaxKind.AmpersandAmpersandToken:
- case SyntaxKind.CommaToken:
- return node === right ? getContextualType(binaryExpression, contextFlags) : undefined;
- default:
- return undefined;
- }
- }
-
- /**
- * Try to find a resolved symbol for an expression without also resolving its type, as
- * getSymbolAtLocation would (as that could be reentrant into contextual typing)
- */
- function getSymbolForExpression(e: Expression) {
- if (e.symbol) {
- return e.symbol;
- }
- if (isIdentifier(e)) {
- return getResolvedSymbol(e);
- }
- if (isPropertyAccessExpression(e)) {
- const lhsType = getTypeOfExpression(e.expression);
- return isPrivateIdentifier(e.name) ? tryGetPrivateIdentifierPropertyOfType(lhsType, e.name) : getPropertyOfType(lhsType, e.name.escapedText);
- }
- return undefined;
-
- function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) {
- const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id);
- return lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol);
- }
- }
-
- // In an assignment expression, the right operand is contextually typed by the type of the left operand.
- // Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand.
- function getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined {
- const kind = getAssignmentDeclarationKind(binaryExpression);
- switch (kind) {
- case AssignmentDeclarationKind.None:
- case AssignmentDeclarationKind.ThisProperty:
- const lhsSymbol = getSymbolForExpression(binaryExpression.left);
- const decl = lhsSymbol && lhsSymbol.valueDeclaration;
- // Unannotated, uninitialized property declarations have a type implied by their usage in the constructor.
- // We avoid calling back into `getTypeOfExpression` and reentering contextual typing to avoid a bogus circularity error in that case.
- if (decl && (isPropertyDeclaration(decl) || isPropertySignature(decl))) {
- const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
- return (overallAnnotation && instantiateType(getTypeFromTypeNode(overallAnnotation), getSymbolLinks(lhsSymbol).mapper)) ||
- (decl.initializer && getTypeOfExpression(binaryExpression.left));
- }
- if (kind === AssignmentDeclarationKind.None) {
- return getTypeOfExpression(binaryExpression.left);
- }
- return getContextualTypeForThisPropertyAssignment(binaryExpression);
- case AssignmentDeclarationKind.Property:
- if (isPossiblyAliasedThisProperty(binaryExpression, kind)) {
- return getContextualTypeForThisPropertyAssignment(binaryExpression);
- }
- // If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration.
- // See `bindStaticPropertyAssignment` in `binder.ts`.
- else if (!binaryExpression.left.symbol) {
- return getTypeOfExpression(binaryExpression.left);
- }
- else {
- const decl = binaryExpression.left.symbol.valueDeclaration;
- if (!decl) {
- return undefined;
- }
- const lhs = cast(binaryExpression.left, isAccessExpression);
- const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
- if (overallAnnotation) {
- return getTypeFromTypeNode(overallAnnotation);
- }
- else if (isIdentifier(lhs.expression)) {
- const id = lhs.expression;
- const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true);
- if (parentSymbol) {
- const annotated = parentSymbol.valueDeclaration && getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration);
- if (annotated) {
- const nameStr = getElementOrPropertyAccessName(lhs);
- if (nameStr !== undefined) {
- return getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr);
- }
- }
- return undefined;
- }
- }
- return isInJSFile(decl) ? undefined : getTypeOfExpression(binaryExpression.left);
- }
- case AssignmentDeclarationKind.ExportsProperty:
- case AssignmentDeclarationKind.Prototype:
- case AssignmentDeclarationKind.PrototypeProperty:
- let valueDeclaration = binaryExpression.left.symbol?.valueDeclaration;
- // falls through
- case AssignmentDeclarationKind.ModuleExports:
- valueDeclaration ||= binaryExpression.symbol?.valueDeclaration;
- const annotated = valueDeclaration && getEffectiveTypeAnnotationNode(valueDeclaration);
- return annotated ? getTypeFromTypeNode(annotated) : undefined;
- case AssignmentDeclarationKind.ObjectDefinePropertyValue:
- case AssignmentDeclarationKind.ObjectDefinePropertyExports:
- case AssignmentDeclarationKind.ObjectDefinePrototypeProperty:
- return Debug.fail("Does not apply");
- default:
- return Debug.assertNever(kind);
- }
- }
-
- function isPossiblyAliasedThisProperty(declaration: BinaryExpression, kind = getAssignmentDeclarationKind(declaration)) {
- if (kind === AssignmentDeclarationKind.ThisProperty) {
- return true;
- }
- if (!isInJSFile(declaration) || kind !== AssignmentDeclarationKind.Property || !isIdentifier((declaration.left as AccessExpression).expression)) {
- return false;
- }
- const name = ((declaration.left as AccessExpression).expression as Identifier).escapedText;
- const symbol = resolveName(declaration.left, name, SymbolFlags.Value, undefined, undefined, /*isUse*/ true, /*excludeGlobals*/ true);
- return isThisInitializedDeclaration(symbol?.valueDeclaration);
- }
-
- function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression): Type | undefined {
- if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left);
- if (binaryExpression.symbol.valueDeclaration) {
- const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration);
- if (annotated) {
- const type = getTypeFromTypeNode(annotated);
- if (type) {
- return type;
- }
- }
- }
- const thisAccess = cast(binaryExpression.left, isAccessExpression);
- if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) {
- return undefined;
- }
- const thisType = checkThisExpression(thisAccess.expression);
- const nameStr = getElementOrPropertyAccessName(thisAccess);
- return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined;
-
- }
-
- function isCircularMappedProperty(symbol: Symbol) {
- return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0);
- }
-
- function getTypeOfPropertyOfContextualType(type: Type, name: __String) {
- return mapType(type, t => {
- if (isGenericMappedType(t)) {
- const constraint = getConstraintTypeFromMappedType(t);
- const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
- const propertyNameType = getStringLiteralType(unescapeLeadingUnderscores(name));
- if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
- return substituteIndexedMappedType(t, propertyNameType);
- }
- }
- else if (t.flags & TypeFlags.StructuredType) {
- const prop = getPropertyOfType(t, name);
- if (prop) {
- return isCircularMappedProperty(prop) ? undefined : getTypeOfSymbol(prop);
- }
- if (isTupleType(t)) {
- const restType = getRestTypeOfTupleType(t);
- if (restType && isNumericLiteralName(name) && +name >= 0) {
- return restType;
- }
- }
- return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
- }
- return undefined;
- }, /*noReductions*/ true);
- }
-
- // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
- // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one
- // exists. Otherwise, it is the type of the string index signature in T, if one exists.
- function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags?: ContextFlags): Type | undefined {
- Debug.assert(isObjectLiteralMethod(node));
- if (node.flags & NodeFlags.InWithStatement) {
- // We cannot answer semantic questions within a with block, do not proceed any further
- return undefined;
- }
- return getContextualTypeForObjectLiteralElement(node, contextFlags);
- }
-
- function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags?: ContextFlags) {
- const objectLiteral = element.parent as ObjectLiteralExpression;
- const propertyAssignmentType = isPropertyAssignment(element) && getContextualTypeForVariableLikeDeclaration(element);
- if (propertyAssignmentType) {
- return propertyAssignmentType;
- }
- const type = getApparentTypeOfContextualType(objectLiteral, contextFlags);
- if (type) {
- if (hasBindableName(element)) {
- // For a (non-symbol) computed property, there is no reason to look up the name
- // in the type. It will just be "__computed", which does not appear in any
- // SymbolTable.
- return getTypeOfPropertyOfContextualType(type, getSymbolOfNode(element).escapedName);
- }
- if (element.name) {
- const nameType = getLiteralTypeFromPropertyName(element.name);
- // We avoid calling getApplicableIndexInfo here because it performs potentially expensive intersection reduction.
- return mapType(type, t => findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType)?.type, /*noReductions*/ true);
- }
- }
- return undefined;
- }
-
- // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is
- // the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature,
- // it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated
- // type of T.
- function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined {
- return arrayContextualType && (
- getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String)
- || mapType(
- arrayContextualType,
- t => getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false),
- /*noReductions*/ true));
- }
-
- // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type.
- function getContextualTypeForConditionalOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined {
- const conditional = node.parent as ConditionalExpression;
- return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined;
- }
-
- function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild) {
- const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName);
- // JSX expression is in children of JSX Element, we will look for an "children" attribute (we get the name from JSX.ElementAttributesProperty)
- const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
- if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) {
- return undefined;
- }
- const realChildren = getSemanticJsxChildren(node.children);
- const childIndex = realChildren.indexOf(child);
- const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName);
- return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => {
- if (isArrayLikeType(t)) {
- return getIndexedAccessType(t, getNumberLiteralType(childIndex));
- }
- else {
- return t;
- }
- }, /*noReductions*/ true));
- }
-
- function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined {
- const exprParent = node.parent;
- return isJsxAttributeLike(exprParent)
- ? getContextualType(node)
- : isJsxElement(exprParent)
- ? getContextualTypeForChildJsxExpression(exprParent, node)
- : undefined;
- }
-
- function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined {
- // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type
- // which is a type of the parameter of the signature we are trying out.
- // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName
- if (isJsxAttribute(attribute)) {
- const attributesType = getApparentTypeOfContextualType(attribute.parent);
- if (!attributesType || isTypeAny(attributesType)) {
- return undefined;
- }
- return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
- }
- else {
- return getContextualType(attribute.parent);
- }
- }
-
- // Return true if the given expression is possibly a discriminant value. We limit the kinds of
- // expressions we check to those that don't depend on their contextual type in order not to cause
- // recursive (and possibly infinite) invocations of getContextualType.
- function isPossiblyDiscriminantValue(node: Expression): boolean {
- switch (node.kind) {
- case SyntaxKind.StringLiteral:
- case SyntaxKind.NumericLiteral:
- case SyntaxKind.BigIntLiteral:
- case SyntaxKind.NoSubstitutionTemplateLiteral:
- case SyntaxKind.TrueKeyword:
- case SyntaxKind.FalseKeyword:
- case SyntaxKind.NullKeyword:
- case SyntaxKind.Identifier:
- case SyntaxKind.UndefinedKeyword:
- return true;
- case SyntaxKind.PropertyAccessExpression:
- case SyntaxKind.ParenthesizedExpression:
- return isPossiblyDiscriminantValue((node as PropertyAccessExpression | ParenthesizedExpression).expression);
- case SyntaxKind.JsxExpression:
- return !(node as JsxExpression).expression || isPossiblyDiscriminantValue((node as JsxExpression).expression!);
- }
- return false;
- }
-
- function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) {
- return getMatchingUnionConstituentForObjectLiteral(contextualType, node) || discriminateTypeByDiscriminableItems(contextualType,
- concatenate(
- map(
- filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)),
- prop => ([() => getContextFreeTypeOfExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String])
- ),
- map(
- filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)),
- s => [() => undefinedType, s.escapedName] as [() => Type, __String]
- )
- ),
- isTypeAssignableTo,
- contextualType
- );
- }
-
- function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) {
- return discriminateTypeByDiscriminableItems(contextualType,
- concatenate(
- map(
- filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))),
- prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => getContextFreeTypeOfExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String])
- ),
- map(
- filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)),
- s => [() => undefinedType, s.escapedName] as [() => Type, __String]
- )
- ),
- isTypeAssignableTo,
- contextualType
- );
- }
-
- // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily
- // be "pushed" onto a node using the contextualType property.
- function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags?: ContextFlags): Type | undefined {
- const contextualType = isObjectLiteralMethod(node) ?
- getContextualTypeForObjectLiteralMethod(node, contextFlags) :
- getContextualType(node, contextFlags);
- const instantiatedType = instantiateContextualType(contextualType, node, contextFlags);
- if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) {
- const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true);
- return apparentType.flags & TypeFlags.Union && isObjectLiteralExpression(node) ? discriminateContextualTypeByObjectMembers(node, apparentType as UnionType) :
- apparentType.flags & TypeFlags.Union && isJsxAttributes(node) ? discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType) :
- apparentType;
- }
- }
-
- // If the given contextual type contains instantiable types and if a mapper representing
- // return type inferences is available, instantiate those types using that mapper.
- function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags?: ContextFlags): Type | undefined {
- if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) {
- const inferenceContext = getInferenceContext(node);
- // If no inferences have been made, nothing is gained from instantiating as type parameters
- // would just be replaced with their defaults similar to the apparent type.
- if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) {
- // For contextual signatures we incorporate all inferences made so far, e.g. from return
- // types as well as arguments to the left in a function call.
- if (contextFlags && contextFlags & ContextFlags.Signature) {
- return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
- }
- // For other purposes (e.g. determining whether to produce literal types) we only
- // incorporate inferences made from the return type in a function call.
- if (inferenceContext.returnMapper) {
- return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper);
- }
- }
- }
- return contextualType;
- }
-
- // This function is similar to instantiateType, except that (a) it only instantiates types that
- // are classified as instantiable (i.e. it doesn't instantiate object types), and (b) it performs
- // no reductions on instantiated union types.
- function instantiateInstantiableTypes(type: Type, mapper: TypeMapper): Type {
- if (type.flags & TypeFlags.Instantiable) {
- return instantiateType(type, mapper);
- }
- if (type.flags & TypeFlags.Union) {
- return getUnionType(map((type as UnionType).types, t => instantiateInstantiableTypes(t, mapper)), UnionReduction.None);
- }
- if (type.flags & TypeFlags.Intersection) {
- return getIntersectionType(map((type as IntersectionType).types, t => instantiateInstantiableTypes(t, mapper)));
- }
- return type;
- }
-
- /**
- * Whoa! Do you really want to use this function?
- *
- * Unless you're trying to get the *non-apparent* type for a
- * value-literal type or you're authoring relevant portions of this algorithm,
- * you probably meant to use 'getApparentTypeOfContextualType'.
- * Otherwise this may not be very useful.
- *
- * In cases where you *are* working on this function, you should understand
- * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'.
- *
- * - Use 'getContextualType' when you are simply going to propagate the result to the expression.
- * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type.
- *
- * @param node the expression whose contextual type will be returned.
- * @returns the contextual type of an expression.
- */
- function getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined {
- if (node.flags & NodeFlags.InWithStatement) {
- // We cannot answer semantic questions within a with block, do not proceed any further
- return undefined;
- }
- if (node.contextualType) {
- return node.contextualType;
- }
- const { parent } = node;
- switch (parent.kind) {
- case SyntaxKind.VariableDeclaration:
- case SyntaxKind.Parameter:
- case SyntaxKind.PropertyDeclaration:
- case SyntaxKind.PropertySignature:
- case SyntaxKind.BindingElement:
- return getContextualTypeForInitializerExpression(node, contextFlags);
- case SyntaxKind.ArrowFunction:
- case SyntaxKind.ReturnStatement:
- return getContextualTypeForReturnExpression(node);
- case SyntaxKind.YieldExpression:
- return getContextualTypeForYieldOperand(parent as YieldExpression);
- case SyntaxKind.AwaitExpression:
- return getContextualTypeForAwaitOperand(parent as AwaitExpression, contextFlags);
- case SyntaxKind.CallExpression:
- case SyntaxKind.NewExpression:
- return getContextualTypeForArgument(parent as CallExpression | NewExpression, node);
- case SyntaxKind.TypeAssertionExpression:
- case SyntaxKind.AsExpression:
- return isConstTypeReference((parent as AssertionExpression).type) ? tryFindWhenConstTypeReference(parent as AssertionExpression) : getTypeFromTypeNode((parent as AssertionExpression).type);
- case SyntaxKind.BinaryExpression:
- return getContextualTypeForBinaryOperand(node, contextFlags);
- case SyntaxKind.PropertyAssignment:
- case SyntaxKind.ShorthandPropertyAssignment:
- return getContextualTypeForObjectLiteralElement(parent as PropertyAssignment | ShorthandPropertyAssignment, contextFlags);
- case SyntaxKind.SpreadAssignment:
- return getContextualType(parent.parent as ObjectLiteralExpression, contextFlags);
- case SyntaxKind.ArrayLiteralExpression: {
- const arrayLiteral = parent as ArrayLiteralExpression;
- const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags);
- return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node));
- }
- case SyntaxKind.ConditionalExpression:
- return getContextualTypeForConditionalOperand(node, contextFlags);
- case SyntaxKind.TemplateSpan:
- Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression);
- return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node);
- case SyntaxKind.ParenthesizedExpression: {
- // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast.
- const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined;
- return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) :
- isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? tryFindWhenConstTypeReference(parent as ParenthesizedExpression) :
- getTypeFromTypeNode(tag.typeExpression.type);
- }
- case SyntaxKind.NonNullExpression:
- return getContextualType(parent as NonNullExpression, contextFlags);
- case SyntaxKind.JsxExpression:
- return getContextualTypeForJsxExpression(parent as JsxExpression);
- case SyntaxKind.JsxAttribute:
- case SyntaxKind.JsxSpreadAttribute:
- return getContextualTypeForJsxAttribute(parent as JsxAttribute | JsxSpreadAttribute);
- case SyntaxKind.JsxOpeningElement:
- case SyntaxKind.JsxSelfClosingElement:
- return getContextualJsxElementAttributesType(parent as JsxOpeningLikeElement, contextFlags);
- }
- return undefined;
-
- function tryFindWhenConstTypeReference(node: Expression) {
- return getContextualType(node);
- }
- }
-
- function getInferenceContext(node: Node) {
- const ancestor = findAncestor(node, n => !!n.inferenceContext);
- return ancestor && ancestor.inferenceContext!;
- }
-
- function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags?: ContextFlags) {
- if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.Completions) {
- // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit
- // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type
- // (as below) instead!
- return node.parent.contextualType;
- }
- return getContextualTypeForArgumentAtIndex(node, 0);
- }
-
- function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) {
- return getJsxReferenceKind(node) !== JsxReferenceKind.Component
- ? getJsxPropsTypeFromCallSignature(signature, node)
- : getJsxPropsTypeFromClassType(signature, node);
- }
-
- function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) {
- let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType);
- propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType);
- const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
- if (!isErrorType(intrinsicAttribs)) {
- propsType = intersectTypes(intrinsicAttribs, propsType);
- }
- return propsType;
- }
-
- function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) {
- if (sig.compositeSignatures) {
- // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input
- // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature,
- // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur
- // for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input.
- // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane.
- const results: Type[] = [];
- for (const signature of sig.compositeSignatures) {
- const instance = getReturnTypeOfSignature(signature);
- if (isTypeAny(instance)) {
- return instance;
- }
- const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation);
- if (!propType) {
- return;
- }
- results.push(propType);
- }
- return getIntersectionType(results); // Same result for both union and intersection signatures
- }
- const instanceType = getReturnTypeOfSignature(sig);
- return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation);
- }
-
- function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) {
- if (isJsxIntrinsicIdentifier(context.tagName)) {
- const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context);
- const fakeSignature = createSignatureForJSXIntrinsic(context, result);
- return getOrCreateTypeFromSignature(fakeSignature);
- }
- const tagType = checkExpressionCached(context.tagName);
- if (tagType.flags & TypeFlags.StringLiteral) {
- const result = getIntrinsicAttributesTypeFromStringLiteralType(tagType as StringLiteralType, context);
- if (!result) {
- return errorType;
- }
- const fakeSignature = createSignatureForJSXIntrinsic(context, result);
- return getOrCreateTypeFromSignature(fakeSignature);
- }
- return tagType;
- }
-
- function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) {
- const managedSym = getJsxLibraryManagedAttributes(ns);
- if (managedSym) {
- const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters
- const ctorType = getStaticTypeOfReferencedJsxConstructor(context);
- if (managedSym.flags & SymbolFlags.TypeAlias) {
- const params = getSymbolLinks(managedSym).typeParameters;
- if (length(params) >= 2) {
- const args = fillMissingTypeArguments([ctorType, attributesType], params, 2, isInJSFile(context));
- return getTypeAliasInstantiation(managedSym, args);
- }
- }
- if (length((declaredManagedType as GenericType).typeParameters) >= 2) {
- const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context));
- return createTypeReference((declaredManagedType as GenericType), args);
- }
- }
- return attributesType;
- }
-
- function getJsxPropsTypeFromClassType(sig: Signature, context: JsxOpeningLikeElement) {
- const ns = getJsxNamespaceAt(context);
- const forcedLookupLocation = getJsxElementPropertiesName(ns);
- let attributesType = forcedLookupLocation === undefined
- // If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type
- ? getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType)
- : forcedLookupLocation === ""
- // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead
- ? getReturnTypeOfSignature(sig)
- // Otherwise get the type of the property on the signature return type
- : getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation);
-
- if (!attributesType) {
- // There is no property named 'props' on this instance type
- if (!!forcedLookupLocation && !!length(context.attributes.properties)) {
- error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation));
- }
- return unknownType;
- }
-
- attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType);
-
- if (isTypeAny(attributesType)) {
- // Props is of type 'any' or unknown
- return attributesType;
- }
- else {
- // Normal case -- add in IntrinsicClassElements and IntrinsicElements
- let apparentAttributesType = attributesType;
- const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context);
- if (!isErrorType(intrinsicClassAttribs)) {
- const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol);
- const hostClassType = getReturnTypeOfSignature(sig);
- apparentAttributesType = intersectTypes(
- typeParams
- ? createTypeReference(intrinsicClassAttribs as GenericType, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isInJSFile(context)))
- : intrinsicClassAttribs,
- apparentAttributesType
- );
- }
-
- const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context);
- if (!isErrorType(intrinsicAttribs)) {
- apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType);
- }
-
- return apparentAttributesType;
- }
- }
-
- function getIntersectedSignatures(signatures: readonly Signature[]) {
- return getStrictOptionValue(compilerOptions, "noImplicitAny")
- ? reduceLeft(
- signatures,
- (left, right) =>
- left === right || !left ? left
- : compareTypeParametersIdentical(left.typeParameters, right.typeParameters) ? combineSignaturesOfIntersectionMembers(left, right)
- : undefined)
- : undefined;
- }
-
- function combineIntersectionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined {
- if (!left || !right) {
- return left || right;
- }
- // A signature `this` type might be a read or a write position... It's very possible that it should be invariant
- // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be
- // pessimistic when contextual typing, for now, we'll union the `this` types.
- const thisType = getUnionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]);
- return createSymbolWithType(left, thisType);
- }
-
- function combineIntersectionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) {
- const leftCount = getParameterCount(left);
- const rightCount = getParameterCount(right);
- const longest = leftCount >= rightCount ? left : right;
- const shorter = longest === left ? right : left;
- const longestCount = longest === left ? leftCount : rightCount;
- const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right));
- const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest);
- const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0));
- for (let i = 0; i < longestCount; i++) {
- let longestParamType = tryGetTypeAtPosition(longest, i)!;
- if (longest === right) {
- longestParamType = instantiateType(longestParamType, mapper);
- }
- let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType;
- if (shorter === right) {
- shorterParamType = instantiateType(shorterParamType, mapper);
- }
- const unionParamType = getUnionType([longestParamType, shorterParamType]);
- const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1);
- const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter);
- const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i);
- const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i);
-
- const paramName = leftName === rightName ? leftName :
- !leftName ? rightName :
- !rightName ? leftName :
- undefined;
- const paramSymbol = createSymbol(
- SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0),
- paramName || `arg${i}` as __String
- );
- paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType;
- params[i] = paramSymbol;
- }
- if (needsExtraRestElement) {
- const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String);
- restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount));
- if (shorter === right) {
- restParamSymbol.type = instantiateType(restParamSymbol.type, mapper);
- }
- params[longestCount] = restParamSymbol;
- }
- return params;
- }
-
- function combineSignaturesOfIntersectionMembers(left: Signature, right: Signature): Signature {
- const typeParams = left.typeParameters || right.typeParameters;
- let paramMapper: TypeMapper | undefined;
- if (left.typeParameters && right.typeParameters) {
- paramMapper = createTypeMapper(right.typeParameters, left.typeParameters);
- // We just use the type parameter defaults from the first signature
- }
- const declaration = left.declaration;
- const params = combineIntersectionParameters(left, right, paramMapper);
- const thisParam = combineIntersectionThisParam(left.thisParameter, right.thisParameter, paramMapper);
- const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount);
- const result = createSignature(
- declaration,
- typeParams,
- thisParam,
- params,
- /*resolvedReturnType*/ undefined,
- /*resolvedTypePredicate*/ undefined,
- minArgCount,
- (left.flags | right.flags) & SignatureFlags.PropagatingFlags
- );
- result.compositeKind = TypeFlags.Intersection;
- result.compositeSignatures = concatenate(left.compositeKind === TypeFlags.Intersection && left.compositeSignatures || [left], [right]);
- if (paramMapper) {
- result.mapper = left.compositeKind === TypeFlags.Intersection && left.mapper && left.compositeSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper;
- }
- return result;
- }
-
- // If the given type is an object or union type with a single signature, and if that signature has at
- // least as many parameters as the given function, return the signature. Otherwise return undefined.
- function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined {
- const signatures = getSignaturesOfType(type, SignatureKind.Call);
- const applicableByArity = filter(signatures, s => !isAritySmaller(s, node));
- return applicableByArity.length === 1 ? applicableByArity[0] : getIntersectedSignatures(applicableByArity);
- }
-
- /** If the contextual signature has fewer parameters than the function expression, do not use it */
- function isAritySmaller(signature: Signature, target: SignatureDeclaration) {
- let targetParameterCount = 0;
- for (; targetParameterCount < target.parameters.length; targetParameterCount++) {
- const param = target.parameters[targetParameterCount];
- if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) {
- break;
- }
- }
- if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) {
- targetParameterCount--;
- }
- return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount;
- }
-
- function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined {
- // Only function expressions, arrow functions, and object literal methods are contextually typed.
- return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node)
- ? getContextualSignature(node as FunctionExpression)
- : undefined;
- }
-
- // Return the contextual signature for a given expression node. A contextual type provides a
- // contextual signature if it has a single call signature and if that call signature is non-generic.
- // If the contextual type is a union type, get the signature from each type possible and if they are
- // all identical ignoring their return type, the result is same signature but with return type as
- // union type of return types from these signatures
- function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined {
- Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
- const typeTagSignature = getSignatureOfTypeTag(node);
- if (typeTagSignature) {
- return typeTagSignature;
- }
- const type = getApparentTypeOfContextualType(node, ContextFlags.Signature);
- if (!type) {
- return undefined;
- }
- if (!(type.flags & TypeFlags.Union)) {
- return getContextualCallSignature(type, node);
- }
- let signatureList: Signature[] | undefined;
- const types = (type as UnionType).types;
- for (const current of types) {
- const signature = getContextualCallSignature(current, node);
- if (signature) {
- if (!signatureList) {
- // This signature will contribute to contextual union signature
- signatureList = [signature];
- }
- else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) {
- // Signatures aren't identical, do not use
- return undefined;
- }
- else {
- // Use this signature for contextual union signature
- signatureList.push(signature);
- }
- }
- }
- // Result is union of signatures collected (return type is union of return types of this signature set)
- if (signatureList) {
- return signatureList.length === 1 ? signatureList[0] : createUnionSignature(signatureList[0], signatureList);
- }
- }
-
- function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type {
- if (languageVersion < ScriptTarget.ES2015) {
- checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray);
- }
-
- const arrayOrIterableType = checkExpression(node.expression, checkMode);
- return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression);
- }
-
- function checkSyntheticExpression(node: SyntheticExpression): Type {
- return node.isSpread ? getIndexedAccessType(node.type, numberType) : node.type;
- }
-
- function hasDefaultValue(node: BindingElement | Expression): boolean {
- return (node.kind === SyntaxKind.BindingElement && !!(node as BindingElement).initializer) ||
- (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken);
- }
-
- function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type {
- const elements = node.elements;
- const elementCount = elements.length;
- const elementTypes: Type[] = [];
- const elementFlags: ElementFlags[] = [];
- const contextualType = getApparentTypeOfContextualType(node);
- const inDestructuringPattern = isAssignmentTarget(node);
- const inConstContext = isConstContext(node);
- let hasOmittedExpression = false;
- for (let i = 0; i < elementCount; i++) {
- const e = elements[i];
- if (e.kind === SyntaxKind.SpreadElement) {
- if (languageVersion < ScriptTarget.ES2015) {
- checkExternalEmitHelpers(e, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray);
- }
- const spreadType = checkExpression((e as SpreadElement).expression, checkMode, forceTuple);
- if (isArrayLikeType(spreadType)) {
- elementTypes.push(spreadType);
- elementFlags.push(ElementFlags.Variadic);
- }
- else if (inDestructuringPattern) {
- // Given the following situation:
- // var c: {};
- // [...c] = ["", 0];
- //
- // c is represented in the tree as a spread element in an array literal.
- // But c really functions as a rest element, and its purpose is to provide
- // a contextual type for the right hand side of the assignment. Therefore,
- // instead of calling checkExpression on "...c", which will give an error
- // if c is not iterable/array-like, we need to act as if we are trying to
- // get the contextual element type from it. So we do something similar to
- // getContextualTypeForElementExpression, which will crucially not error
- // if there is no index type / iterated type.
- const restElementType = getIndexTypeOfType(spreadType, numberType) ||
- getIteratedTypeOrElementType(IterationUse.Destructuring, spreadType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false) ||
- unknownType;
- elementTypes.push(restElementType);
- elementFlags.push(ElementFlags.Rest);
- }
- else {
- elementTypes.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, (e as SpreadElement).expression));
- elementFlags.push(ElementFlags.Rest);
- }
- }
- else if (exactOptionalPropertyTypes && e.kind === SyntaxKind.OmittedExpression) {
- hasOmittedExpression = true;
- elementTypes.push(missingType);
- elementFlags.push(ElementFlags.Optional);
- }
- else {
- const elementContextualType = getContextualTypeForElementExpression(contextualType, elementTypes.length);
- const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple);
- elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression));
- elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required);
- }
- }
- if (inDestructuringPattern) {
- return createTupleType(elementTypes, elementFlags);
- }
- if (forceTuple || inConstContext || contextualType && someType(contextualType, isTupleLikeType)) {
- return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext));
- }
- return createArrayLiteralType(createArrayType(elementTypes.length ?
- getUnionType(sameMap(elementTypes, (t, i) => elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessTypeOrUndefined(t, numberType) || anyType : t), UnionReduction.Subtype) :
- strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext));
- }
-
- function createArrayLiteralType(type: Type) {
- if (!(getObjectFlags(type) & ObjectFlags.Reference)) {
- return type;
- }
- let literalType = (type as TypeReference).literalType;
- if (!literalType) {
- literalType = (type as TypeReference).literalType = cloneTypeReference(type as TypeReference);
- literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
- }
- return literalType;
- }
-
- function isNumericName(name: DeclarationName): boolean {
- switch (name.kind) {
- case SyntaxKind.ComputedPropertyName:
- return isNumericComputedName(name);
- case SyntaxKind.Identifier:
- return isNumericLiteralName(name.escapedText);
- case SyntaxKind.NumericLiteral:
- case SyntaxKind.StringLiteral:
- return isNumericLiteralName(name.text);
- default:
- return false;
- }
- }
-
- function isNumericComputedName(name: ComputedPropertyName): boolean {
- // It seems odd to consider an expression of type Any to result in a numeric name,
- // but this behavior is consistent with checkIndexedAccess
- return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike);
- }
-
- function isNumericLiteralName(name: string | __String) {
- // The intent of numeric names is that
- // - they are names with text in a numeric form, and that
- // - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit',
- // acquired by applying the abstract 'ToNumber' operation on the name's text.
- //
- // The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name.
- // In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold.
- //
- // Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)'
- // according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'.
- // Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names
- // because their 'ToString' representation is not equal to their original text.
- // This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1.
- //
- // Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'.
- // The '+' prefix operator is equivalent here to applying the abstract ToNumber operation.
- // Applying the 'toString()' method on a number gives us the abstract ToString operation on a number.
- //
- // Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional.
- // This is desired behavior, because when indexing with them as numeric entities, you are indexing
- // with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively.
- return (+name).toString() === name;
- }
-
- function checkComputedPropertyName(node: ComputedPropertyName): Type {
- const links = getNodeLinks(node.expression);
- if (!links.resolvedType) {
- links.resolvedType = checkExpression(node.expression);
- // The computed property name of a non-static class field within a loop must be stored in a block-scoped binding.
- // (It needs to be bound at class evaluation time.)
- if (isPropertyDeclaration(node.parent) && !hasStaticModifier(node.parent) && isClassExpression(node.parent.parent)) {
- const container = getEnclosingBlockScopeContainer(node.parent.parent);
- const enclosingIterationStatement = getEnclosingIterationStatement(container);
- if (enclosingIterationStatement) {
- // The computed field name will use a block scoped binding which can be unique for each iteration of the loop.
- getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
- // The generated variable which stores the computed field name must be block-scoped.
- getNodeLinks(node).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
- // The generated variable which stores the class must be block-scoped.
- getNodeLinks(node.parent.parent).flags |= NodeCheckFlags.BlockScopedBindingInLoop;
- }
- }
- // This will allow types number, string, symbol or any. It will also allow enums, the unknown
- // type, and any union of these types (like string | number).
- if (links.resolvedType.flags & TypeFlags.Nullable ||
- !isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) &&
- !isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) {
- error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any);
- }
- }
-
- return links.resolvedType;
- }
-
- function isSymbolWithNumericName(symbol: Symbol) {
- const firstDecl = symbol.declarations?.[0];
- return isNumericLiteralName(symbol.escapedName) || (firstDecl && isNamedDeclaration(firstDecl) && isNumericName(firstDecl.name));
- }
-
- function isSymbolWithSymbolName(symbol: Symbol) {
- const firstDecl = symbol.declarations?.[0];
- return isKnownSymbol(symbol) || (firstDecl && isNamedDeclaration(firstDecl) && isComputedPropertyName(firstDecl.name) &&
- isTypeAssignableToKind(checkComputedPropertyName(firstDecl.name), TypeFlags.ESSymbol));
- }
-
- function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], keyType: Type): IndexInfo {
- const propTypes: Type[] = [];
- for (let i = offset; i < properties.length; i++) {
- const prop = properties[i];
- if (keyType === stringType && !isSymbolWithSymbolName(prop) ||
- keyType === numberType && isSymbolWithNumericName(prop) ||
- keyType === esSymbolType && isSymbolWithSymbolName(prop)) {
- propTypes.push(getTypeOfSymbol(properties[i]));
- }
- }
- const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType;
- return createIndexInfo(keyType, unionType, isConstContext(node));
- }
-
- function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined {
- Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here.");
- const links = getSymbolLinks(symbol);
- if (!links.immediateTarget) {
- const node = getDeclarationOfAliasSymbol(symbol);
- if (!node) return Debug.fail();
- links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true);
- }
-
- return links.immediateTarget;
- }
-
- function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type {
- const inDestructuringPattern = isAssignmentTarget(node);
- // Grammar checking
- checkGrammarObjectLiteralExpression(node, inDestructuringPattern);
-
- const allPropertiesTable = strictNullChecks ? createSymbolTable() : undefined;
- let propertiesTable = createSymbolTable();
- let propertiesArray: Symbol[] = [];
- let spread: Type = emptyObjectType;
-
- const contextualType = getApparentTypeOfContextualType(node);
- const contextualTypeHasPattern = contextualType && contextualType.pattern &&
- (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
- const inConstContext = isConstContext(node);
- const checkFlags = inConstContext ? CheckFlags.Readonly : 0;
- const isInJavascript = isInJSFile(node) && !isInJsonFile(node);
- const enumTag = getJSDocEnumTag(node);
- const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag;
- let objectFlags: ObjectFlags = freshObjectLiteralFlag;
- let patternWithComputedProperties = false;
- let hasComputedStringProperty = false;
- let hasComputedNumberProperty = false;
- let hasComputedSymbolProperty = false;
-
- // Spreads may cause an early bail; ensure computed names are always checked (this is cached)
- // As otherwise they may not be checked until exports for the type at this position are retrieved,
- // which may never occur.
- for (const elem of node.properties) {
- if (elem.name && isComputedPropertyName(elem.name)) {
- checkComputedPropertyName(elem.name);
- }
- }
-
- let offset = 0;
- for (const memberDecl of node.properties) {
- let member = getSymbolOfNode(memberDecl);
- const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName ?
- checkComputedPropertyName(memberDecl.name) : undefined;
- if (memberDecl.kind === SyntaxKind.PropertyAssignment ||
- memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ||
- isObjectLiteralMethod(memberDecl)) {
- let type = memberDecl.kind === SyntaxKind.PropertyAssignment ? checkPropertyAssignment(memberDecl, checkMode) :
- // avoid resolving the left side of the ShorthandPropertyAssignment outside of the destructuring
- // for error recovery purposes. For example, if a user wrote `{ a = 100 }` instead of `{ a: 100 }`.
- // we don't want to say "could not find 'a'".
- memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ? checkExpressionForMutableLocation(!inDestructuringPattern && memberDecl.objectAssignmentInitializer ? memberDecl.objectAssignmentInitializer : memberDecl.name, checkMode) :
- checkObjectLiteralMethod(memberDecl, checkMode);
- if (isInJavascript) {
- const jsDocType = getTypeForDeclarationFromJSDocComment(memberDecl);
- if (jsDocType) {
- checkTypeAssignableTo(type, jsDocType, memberDecl);
- type = jsDocType;
- }
- else if (enumTag && enumTag.typeExpression) {
- checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl);
- }
- }
- objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags;
- const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined;
- const prop = nameType ?
- createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) :
- createSymbol(SymbolFlags.Property | member.flags, member.escapedName, checkFlags);
- if (nameType) {
- prop.nameType = nameType;
- }
-
- if (inDestructuringPattern) {
- // If object literal is an assignment pattern and if the assignment pattern specifies a default value
- // for the property, make the property optional.
- const isOptional =
- (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) ||
- (memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer);
- if (isOptional) {
- prop.flags |= SymbolFlags.Optional;
- }
- }
- else if (contextualTypeHasPattern && !(getObjectFlags(contextualType) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
- // If object literal is contextually typed by the implied type of a binding pattern, and if the
- // binding pattern specifies a default value for the property, make the property optional.
- const impliedProp = getPropertyOfType(contextualType, member.escapedName);
- if (impliedProp) {
- prop.flags |= impliedProp.flags & SymbolFlags.Optional;
- }
-
- else if (!compilerOptions.suppressExcessPropertyErrors && !getIndexInfoOfType(contextualType, stringType)) {
- error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
- symbolToString(member), typeToString(contextualType));
- }
- }
-
- prop.declarations = member.declarations;
- prop.parent = member.parent;
- if (member.valueDeclaration) {
- prop.valueDeclaration = member.valueDeclaration;
- }
-
- prop.type = type;
- prop.target = member;
- member = prop;
- allPropertiesTable?.set(prop.escapedName, prop);
- }
- else if (memberDecl.kind === SyntaxKind.SpreadAssignment) {
- if (languageVersion < ScriptTarget.ES2015) {
- checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign);
- }
- if (propertiesArray.length > 0) {
- spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext);
- propertiesArray = [];
- propertiesTable = createSymbolTable();
- hasComputedStringProperty = false;
- hasComputedNumberProperty = false;
- hasComputedSymbolProperty = false;
- }
- const type = getReducedType(checkExpression(memberDecl.expression));
- if (isValidSpreadType(type)) {
- const mergedType = tryMergeUnionOfObjectTypeAndEmptyObject(type, inConstContext);
- if (allPropertiesTable) {
- checkSpreadPropOverrides(mergedType, allPropertiesTable, memberDecl);
- }
- offset = propertiesArray.length;
- if (isErrorType(spread)) {
- continue;
- }
- spread = getSpreadType(spread, mergedType, node.symbol, objectFlags, inConstContext);
- }
- else {
- error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types);
- spread = errorType;
- }
- continue;
- }
- else {
- // TypeScript 1.0 spec (April 2014)
- // A get accessor declaration is processed in the same manner as
- // an ordinary function declaration(section 6.1) with no parameters.
- // A set accessor declaration is processed in the same manner
- // as an ordinary function declaration with a single parameter and a Void return type.
- Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor);
- checkNodeDeferred(memberDecl);
- }
-
- if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) {
- if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) {
- if (isTypeAssignableTo(computedNameType, numberType)) {
- hasComputedNumberProperty = true;
- }
- else if (isTypeAssignableTo(computedNameType, esSymbolType)) {
- hasComputedSymbolProperty = true;
- }
- else {
- hasComputedStringProperty = true;
- }
- if (inDestructuringPattern) {
- patternWithComputedProperties = true;
- }
- }
- }
- else {
- propertiesTable.set(member.escapedName, member);
- }
- propertiesArray.push(member);
- }
-
- // If object literal is contextually typed by the implied type of a binding pattern, augment the result
- // type with those properties for which the binding pattern specifies a default value.
- // If the object literal is spread into another object literal, skip this step and let the top-level object
- // literal handle it instead.
- if (contextualTypeHasPattern && node.parent.kind !== SyntaxKind.SpreadAssignment) {
- for (const prop of getPropertiesOfType(contextualType)) {
- if (!propertiesTable.get(prop.escapedName) && !getPropertyOfType(spread, prop.escapedName)) {
- if (!(prop.flags & SymbolFlags.Optional)) {
- error(prop.valueDeclaration || (prop as TransientSymbol).bindingElement,
- Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
- }
- propertiesTable.set(prop.escapedName, prop);
- propertiesArray.push(prop);
- }
- }
- }
-
- if (isErrorType(spread)) {
- return errorType;
- }
-
- if (spread !== emptyObjectType) {
- if (propertiesArray.length > 0) {
- spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext);
- propertiesArray = [];
- propertiesTable = createSymbolTable();
- hasComputedStringProperty = false;
- hasComputedNumberProperty = false;
- }
- // remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site
- return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t);
- }
-
- return createObjectLiteralType();
-
- function createObjectLiteralType() {
- const indexInfos = [];
- if (hasComputedStringProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, stringType));
- if (hasComputedNumberProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, numberType));
- if (hasComputedSymbolProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, esSymbolType));
- const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, indexInfos);
- result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
- if (isJSObjectLiteral) {
- result.objectFlags |= ObjectFlags.JSLiteral;
- }
- if (patternWithComputedProperties) {
- result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties;
- }
- if (inDestructuringPattern) {
- result.pattern = node;
- }
- return result;
- }
- }
-
- function isValidSpreadType(type: Type): boolean {
- if (type.flags & TypeFlags.Instantiable) {
- const constraint = getBaseConstraintOfType(type);
- if (constraint !== undefined) {
- return isValidSpreadType(constraint);
- }
- }
- return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) ||
- getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) ||
- type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isValidSpreadType));
- }
-
- function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) {
- checkJsxOpeningLikeElementOrOpeningFragment(node);
- }
-
- function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type {
- checkNodeDeferred(node);
- return getJsxElementTypeAt(node) || anyType;
- }
-
- function checkJsxElementDeferred(node: JsxElement) {
- // Check attributes
- checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement);
-
- // Perform resolution on the closing tag so that rename/go to definition/etc work
- if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) {
- getIntrinsicTagSymbol(node.closingElement);
- }
- else {
- checkExpression(node.closingElement.tagName);
- }
-
- checkJsxChildren(node);
- }
-
- function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type {
- checkNodeDeferred(node);
-
- return getJsxElementTypeAt(node) || anyType;
- }
-
- function checkJsxFragment(node: JsxFragment): Type {
- checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment);
-
- // by default, jsx:'react' will use jsxFactory = React.createElement and jsxFragmentFactory = React.Fragment
- // if jsxFactory compiler option is provided, ensure jsxFragmentFactory compiler option or @jsxFrag pragma is provided too
- const nodeSourceFile = getSourceFileOfNode(node);
- if (getJSXTransformEnabled(compilerOptions) && (compilerOptions.jsxFactory || nodeSourceFile.pragmas.has("jsx"))
- && !compilerOptions.jsxFragmentFactory && !nodeSourceFile.pragmas.has("jsxfrag")) {
- error(node, compilerOptions.jsxFactory
- ? Diagnostics.The_jsxFragmentFactory_compiler_option_must_be_provided_to_use_JSX_fragments_with_the_jsxFactory_compiler_option
- : Diagnostics.An_jsxFrag_pragma_is_required_when_using_an_jsx_pragma_with_JSX_fragments);
- }
-
- checkJsxChildren(node);
- return getJsxElementTypeAt(node) || anyType;
- }
-
- function isHyphenatedJsxName(name: string | __String) {
- return stringContains(name as string, "-");
- }
-
- /**
- * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name
- */
- function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean {
- return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText);
- }
-
- function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) {
- return node.initializer
- ? checkExpressionForMutableLocation(node.initializer, checkMode)
- : trueType; // is sugar for
- }
-
- /**
- * Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element.
- *
- * @param openingLikeElement a JSX opening-like element
- * @param filter a function to remove attributes that will not participate in checking whether attributes are assignable
- * @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property.
- * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral,
- * which also calls getSpreadType.
- */
- function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) {
- const attributes = openingLikeElement.attributes;
- const allAttributesTable = strictNullChecks ? createSymbolTable() : undefined;
- let attributesTable = createSymbolTable();
- let spread: Type = emptyJsxObjectType;
- let hasSpreadAnyType = false;
- let typeToIntersect: Type | undefined;
- let explicitlySpecifyChildrenAttribute = false;
- let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes;
- const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement));
-
- for (const attributeDecl of attributes.properties) {
- const member = attributeDecl.symbol;
- if (isJsxAttribute(attributeDecl)) {
- const exprType = checkJsxAttribute(attributeDecl, checkMode);
- objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags;
-
- const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName);
- attributeSymbol.declarations = member.declarations;
- attributeSymbol.parent = member.parent;
- if (member.valueDeclaration) {
- attributeSymbol.valueDeclaration = member.valueDeclaration;
- }
- attributeSymbol.type = exprType;
- attributeSymbol.target = member;
- attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
- allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol);
- if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
- explicitlySpecifyChildrenAttribute = true;
- }
- }
- else {
- Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute);
- if (attributesTable.size > 0) {
- spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false);
- attributesTable = createSymbolTable();
- }
- const exprType = getReducedType(checkExpressionCached(attributeDecl.expression, checkMode));
- if (isTypeAny(exprType)) {
- hasSpreadAnyType = true;
- }
- if (isValidSpreadType(exprType)) {
- spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false);
- if (allAttributesTable) {
- checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl);
- }
- }
- else {
- typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType;
- }
- }
- }
-
- if (!hasSpreadAnyType) {
- if (attributesTable.size > 0) {
- spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false);
- }
- }
-
- // Handle children attribute
- const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined;
- // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement
- if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) {
- const childrenTypes: Type[] = checkJsxChildren(parent, checkMode);
-
- if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
- // Error if there is a attribute named "children" explicitly specified and children element.
- // This is because children element will overwrite the value from attributes.
- // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread.
- if (explicitlySpecifyChildrenAttribute) {
- error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName));
- }
-
- const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes);
- const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName);
- // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process
- const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName);
- childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] :
- childrenContextualType && someType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) :
- createArrayType(getUnionType(childrenTypes));
- // Fake up a property declaration for the children
- childrenPropSymbol.valueDeclaration = factory.createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined);
- setParent(childrenPropSymbol.valueDeclaration, attributes);
- childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol;
- const childPropMap = createSymbolTable();
- childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol);
- spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, emptyArray),
- attributes.symbol, objectFlags, /*readonly*/ false);
-
- }
- }
-
- if (hasSpreadAnyType) {
- return anyType;
- }
- if (typeToIntersect && spread !== emptyJsxObjectType) {
- return getIntersectionType([typeToIntersect, spread]);
- }
- return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread);
-
- /**
- * Create anonymous type from given attributes symbol table.
- * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable
- * @param attributesTable a symbol table of attributes property
- */
- function createJsxAttributesType() {
- objectFlags |= freshObjectLiteralFlag;
- const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, emptyArray);
- result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
- return result;
- }
- }
-
- function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) {
- const childrenTypes: Type[] = [];
- for (const child of node.children) {
- // In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that
- // because then type of children property will have constituent of string type.
- if (child.kind === SyntaxKind.JsxText) {
- if (!child.containsOnlyTriviaWhiteSpaces) {
- childrenTypes.push(stringType);
- }
- }
- else if (child.kind === SyntaxKind.JsxExpression && !child.expression) {
- continue; // empty jsx expressions don't *really* count as present children
- }
- else {
- childrenTypes.push(checkExpressionForMutableLocation(child, checkMode));
- }
- }
- return childrenTypes;
- }
-
- function checkSpreadPropOverrides(type: Type, props: SymbolTable, spread: SpreadAssignment | JsxSpreadAttribute) {
- for (const right of getPropertiesOfType(type)) {
- if (!(right.flags & SymbolFlags.Optional)) {
- const left = props.get(right.escapedName);
- if (left) {
- const diagnostic = error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName));
- addRelatedInfo(diagnostic, createDiagnosticForNode(spread, Diagnostics.This_spread_always_overwrites_this_property));
- }
- }
- }
- }
-
- /**
- * Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element.
- * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used)
- * @param node a JSXAttributes to be resolved of its type
- */
- function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode | undefined) {
- return createJsxAttributesTypeFromAttributesProperty(node.parent, checkMode);
- }
-
- function getJsxType(name: __String, location: Node | undefined) {
- const namespace = getJsxNamespaceAt(location);
- const exports = namespace && getExportsOfSymbol(namespace);
- const typeSymbol = exports && getSymbol(exports, name, SymbolFlags.Type);
- return typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType;
- }
-
- /**
- * Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic
- * property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic
- * string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement).
- * May also return unknownSymbol if both of these lookups fail.
- */
- function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
- const links = getNodeLinks(node);
- if (!links.resolvedSymbol) {
- const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node);
- if (!isErrorType(intrinsicElementsType)) {
- // Property case
- if (!isIdentifier(node.tagName)) return Debug.fail();
- const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText);
- if (intrinsicProp) {
- links.jsxFlags |= JsxFlags.IntrinsicNamedElement;
- return links.resolvedSymbol = intrinsicProp;
- }
-
- // Intrinsic string indexer case
- const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType);
- if (indexSignatureType) {
- links.jsxFlags |= JsxFlags.IntrinsicIndexedElement;
- return links.resolvedSymbol = intrinsicElementsType.symbol;
- }
-
- // Wasn't found
- error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements);
- return links.resolvedSymbol = unknownSymbol;
- }
- else {
- if (noImplicitAny) {
- error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, unescapeLeadingUnderscores(JsxNames.IntrinsicElements));
- }
- return links.resolvedSymbol = unknownSymbol;
- }
- }
- return links.resolvedSymbol;
- }
-
- function getJsxNamespaceContainerForImplicitImport(location: Node | undefined): Symbol | undefined {
- const file = location && getSourceFileOfNode(location);
- const links = file && getNodeLinks(file);
- if (links && links.jsxImplicitImportContainer === false) {
- return undefined;
- }
- if (links && links.jsxImplicitImportContainer) {
- return links.jsxImplicitImportContainer;
- }
- const runtimeImportSpecifier = getJSXRuntimeImport(getJSXImplicitImportBase(compilerOptions, file), compilerOptions);
- if (!runtimeImportSpecifier) {
- return undefined;
- }
- const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic;
- const errorMessage = isClassic
- ? Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option
- : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations;
- const mod = resolveExternalModule(location!, runtimeImportSpecifier, errorMessage, location!);
- const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined;
- if (links) {
- links.jsxImplicitImportContainer = result || false;
- }
- return result;
- }
-
- function getJsxNamespaceAt(location: Node | undefined): Symbol {
- const links = location && getNodeLinks(location);
- if (links && links.jsxNamespace) {
- return links.jsxNamespace;
- }
- if (!links || links.jsxNamespace !== false) {
- let resolvedNamespace = getJsxNamespaceContainerForImplicitImport(location);
-
- if (!resolvedNamespace || resolvedNamespace === unknownSymbol) {
- const namespaceName = getJsxNamespace(location);
- resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false);
- }
-
- if (resolvedNamespace) {
- const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace));
- if (candidate && candidate !== unknownSymbol) {
- if (links) {
- links.jsxNamespace = candidate;
- }
- return candidate;
- }
- }
- if (links) {
- links.jsxNamespace = false;
- }
- }
- // JSX global fallback
- const s = resolveSymbol(getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined));
- if (s === unknownSymbol) {
- return undefined!; // TODO: GH#18217
- }
- return s!; // TODO: GH#18217
- }
-
- /**
- * Look into JSX namespace and then look for container with matching name as nameOfAttribPropContainer.
- * Get a single property from that container if existed. Report an error if there are more than one property.
- *
- * @param nameOfAttribPropContainer a string of value JsxNames.ElementAttributesPropertyNameContainer or JsxNames.ElementChildrenAttributeNameContainer
- * if other string is given or the container doesn't exist, return undefined.
- */
- function getNameFromJsxElementAttributesContainer(nameOfAttribPropContainer: __String, jsxNamespace: Symbol): __String | undefined {
- // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [symbol]
- const jsxElementAttribPropInterfaceSym = jsxNamespace && getSymbol(jsxNamespace.exports!, nameOfAttribPropContainer, SymbolFlags.Type);
- // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [type]
- const jsxElementAttribPropInterfaceType = jsxElementAttribPropInterfaceSym && getDeclaredTypeOfSymbol(jsxElementAttribPropInterfaceSym);
- // The properties of JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute
- const propertiesOfJsxElementAttribPropInterface = jsxElementAttribPropInterfaceType && getPropertiesOfType(jsxElementAttribPropInterfaceType);
- if (propertiesOfJsxElementAttribPropInterface) {
- // Element Attributes has zero properties, so the element attributes type will be the class instance type
- if (propertiesOfJsxElementAttribPropInterface.length === 0) {
- return "" as __String;
- }
- // Element Attributes has one property, so the element attributes type will be the type of the corresponding
- // property of the class instance type
- else if (propertiesOfJsxElementAttribPropInterface.length === 1) {
- return propertiesOfJsxElementAttribPropInterface[0].escapedName;
- }
- else if (propertiesOfJsxElementAttribPropInterface.length > 1 && jsxElementAttribPropInterfaceSym.declarations) {
- // More than one property on ElementAttributesProperty is an error
- error(jsxElementAttribPropInterfaceSym.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer));
- }
- }
- return undefined;
- }
-
- function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) {
- // JSX.LibraryManagedAttributes [symbol]
- return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type);
- }
-
- /// e.g. "props" for React.d.ts,
- /// or 'undefined' if ElementAttributesProperty doesn't exist (which means all
- /// non-intrinsic elements' attributes type is 'any'),
- /// or '' if it has 0 properties (which means every
- /// non-intrinsic elements' attributes type is the element instance type)
- function getJsxElementPropertiesName(jsxNamespace: Symbol) {
- return getNameFromJsxElementAttributesContainer(JsxNames.ElementAttributesPropertyNameContainer, jsxNamespace);
- }
-
- function getJsxElementChildrenPropertyName(jsxNamespace: Symbol): __String | undefined {
- return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace);
- }
-
- function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): readonly Signature[] {
- if (elementType.flags & TypeFlags.String) {
- return [anySignature];
- }
- else if (elementType.flags & TypeFlags.StringLiteral) {
- const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(elementType as StringLiteralType, caller);
- if (!intrinsicType) {
- error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements);
- return emptyArray;
- }
- else {
- const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType);
- return [fakeSignature];
- }
- }
- const apparentElemType = getApparentType(elementType);
- // Resolve the signatures, preferring constructor
- let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct);
- if (signatures.length === 0) {
- // No construct signatures, try call signatures
- signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call);
- }
- if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) {
- // If each member has some combination of new/call signatures; make a union signature list for those
- signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller)));
- }
- return signatures;
- }
-
- function getIntrinsicAttributesTypeFromStringLiteralType(type: StringLiteralType, location: Node): Type | undefined {
- // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type
- // For example:
- // var CustomTag: "h1" = "h1";
- // Hello World
- const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location);
- if (!isErrorType(intrinsicElementsType)) {
- const stringLiteralTypeName = type.value;
- const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName));
- if (intrinsicProp) {
- return getTypeOfSymbol(intrinsicProp);
- }
- const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType);
- if (indexSignatureType) {
- return indexSignatureType;
- }
- return undefined;
- }
- // If we need to report an error, we already done so here. So just return any to prevent any more error downstream
- return anyType;
- }
-
- function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: JsxOpeningLikeElement) {
- if (refKind === JsxReferenceKind.Function) {
- const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
- if (sfcReturnConstraint) {
- checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_return_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain);
- }
- }
- else if (refKind === JsxReferenceKind.Component) {
- const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
- if (classConstraint) {
- // Issue an error if this return type isn't assignable to JSX.ElementClass, failing that
- checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_instance_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain);
- }
- }
- else { // Mixed
- const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
- const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
- if (!sfcReturnConstraint || !classConstraint) {
- return;
- }
- const combined = getUnionType([sfcReturnConstraint, classConstraint]);
- checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_element_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain);
- }
-
- function generateInitialErrorChain(): DiagnosticMessageChain {
- const componentName = getTextOfNode(openingLikeElement.tagName);
- return chainDiagnosticMessages(/* details */ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName);
- }
- }
-
- /**
- * Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name.
- * The function is intended to be called from a function which has checked that the opening element is an intrinsic element.
- * @param node an intrinsic JSX opening-like element
- */
- function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type {
- Debug.assert(isJsxIntrinsicIdentifier(node.tagName));
- const links = getNodeLinks(node);
- if (!links.resolvedJsxElementAttributesType) {
- const symbol = getIntrinsicTagSymbol(node);
- if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) {
- return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol) || errorType;
- }
- else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) {
- return links.resolvedJsxElementAttributesType =
- getIndexTypeOfType(getJsxType(JsxNames.IntrinsicElements, node), stringType) || errorType;
- }
- else {
- return links.resolvedJsxElementAttributesType = errorType;
- }
- }
- return links.resolvedJsxElementAttributesType;
- }
-
- function getJsxElementClassTypeAt(location: Node): Type | undefined {
- const type = getJsxType(JsxNames.ElementClass, location);
- if (isErrorType(type)) return undefined;
- return type;
- }
-
- function getJsxElementTypeAt(location: Node): Type {
- return getJsxType(JsxNames.Element, location);
- }
-
- function getJsxStatelessElementTypeAt(location: Node): Type | undefined {
- const jsxElementType = getJsxElementTypeAt(location);
- if (jsxElementType) {
- return getUnionType([jsxElementType, nullType]);
- }
- }
-
- /**
- * Returns all the properties of the Jsx.IntrinsicElements interface
- */
- function getJsxIntrinsicTagNamesAt(location: Node): Symbol[] {
- const intrinsics = getJsxType(JsxNames.IntrinsicElements, location);
- return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray;
- }
-
- function checkJsxPreconditions(errorNode: Node) {
- // Preconditions for using JSX
- if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) {
- error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided);
- }
-
- if (getJsxElementTypeAt(errorNode) === undefined) {
- if (noImplicitAny) {
- error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist);
- }
- }
- }
-
- function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) {
- const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node);
-
- if (isNodeOpeningLikeElement) {
- checkGrammarJsxElement(node as JsxOpeningLikeElement);
- }
-
- checkJsxPreconditions(node);
-
- 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.Cannot_find_name_0 : undefined;
- const jsxFactoryNamespace = getJsxNamespace(node);
- const jsxFactoryLocation = isNodeOpeningLikeElement ? (node as JsxOpeningLikeElement).tagName : node;
-
- // allow null as jsxFragmentFactory
- let jsxFactorySym: Symbol | undefined;
- if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) {
- jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, jsxFactoryNamespace, /*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 (jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) {
- markAliasSymbolAsReferenced(jsxFactorySym);
- }
- }
-
- // For JsxFragment, mark jsx pragma as referenced via resolveName
- if (isJsxOpeningFragment(node)) {
- const file = getSourceFileOfNode(node);
- const localJsxNamespace = getLocalJsxNamespace(file);
- if (localJsxNamespace) {
- resolveName(jsxFactoryLocation, localJsxNamespace, SymbolFlags.Value, jsxFactoryRefErr, localJsxNamespace, /*isUse*/ true);
- }
- }
- }
-
- if (isNodeOpeningLikeElement) {
- const jsxOpeningLikeNode = node as JsxOpeningLikeElement;
- const sig = getResolvedSignature(jsxOpeningLikeNode);
- checkDeprecatedSignature(sig, node as JsxOpeningLikeElement);
- checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode);
- }
- }
-
- /**
- * Check if a property with the given name is known anywhere in the given type. In an object type, a property
- * is considered known if
- * 1. the object type is empty and the check is for assignability, or
- * 2. if the object type has index signatures, or
- * 3. if the property is actually declared in the object type
- * (this means that 'toString', for example, is not usually a known property).
- * 4. In a union or intersection type,
- * a property is considered known if it is known in any constituent type.
- * @param targetType a type to search a given name in
- * @param name a property name to search
- * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType
- */
- function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean {
- if (targetType.flags & TypeFlags.Object) {
- // For backwards compatibility a symbol-named property is satisfied by a string index signature. This
- // is incorrect and inconsistent with element access expressions, where it is an error, so eventually
- // we should remove this exception.
- if (getPropertyOfObjectType(targetType, name) ||
- getApplicableIndexInfoForName(targetType, name) ||
- isLateBoundName(name) && getIndexInfoOfType(targetType, stringType) ||
- isComparingJsxAttributes && isHyphenatedJsxName(name)) {
- // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
- return true;
- }
- }
- else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) {
- for (const t of (targetType as UnionOrIntersectionType).types) {
- if (isKnownProperty(t, name, isComparingJsxAttributes)) {
- return true;
- }
- }
- }
- return false;
- }
-
- function isExcessPropertyCheckTarget(type: Type): boolean {
- return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) ||
- type.flags & TypeFlags.NonPrimitive ||
- type.flags & TypeFlags.Union && some((type as UnionType).types, isExcessPropertyCheckTarget) ||
- type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isExcessPropertyCheckTarget));
- }
-
- function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) {
- checkGrammarJsxExpression(node);
- if (node.expression) {
- const type = checkExpression(node.expression, checkMode);
- if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) {
- error(node, Diagnostics.JSX_spread_child_must_be_an_array_type);
- }
- return type;
- }
- else {
- return errorType;
- }
- }
-
- function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags {
- return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0;
- }
-
- /**
- * Return whether this symbol is a member of a prototype somewhere
- * Note that this is not tracked well within the compiler, so the answer may be incorrect.
- */
- function isPrototypeProperty(symbol: Symbol) {
- if (symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod) {
- return true;
- }
- if (isInJSFile(symbol.valueDeclaration)) {
- const parent = symbol.valueDeclaration!.parent;
- return parent && isBinaryExpression(parent) &&
- getAssignmentDeclarationKind(parent) === AssignmentDeclarationKind.PrototypeProperty;
- }
- }
-
- /**
- * Check whether the requested property access is valid.
- * Returns true if node is a valid property access, and false otherwise.
- * @param node The node to be checked.
- * @param isSuper True if the access is from `super.`.
- * @param type The type of the object whose property is being accessed. (Not the type of the property.)
- * @param prop The symbol for the property being accessed.
- */
- function checkPropertyAccessibility(
- node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement,
- isSuper: boolean, writing: boolean, type: Type, prop: Symbol, reportError = true): boolean {
-
- const errorNode = !reportError ? undefined :
- node.kind === SyntaxKind.QualifiedName ? node.right :
- node.kind === SyntaxKind.ImportType ? node :
- node.kind === SyntaxKind.BindingElement && node.propertyName ? node.propertyName : node.name;
-
- return checkPropertyAccessibilityAtLocation(node, isSuper, writing, type, prop, errorNode);
- }
-
- /**
- * Check whether the requested property can be accessed at the requested location.
- * Returns true if node is a valid property access, and false otherwise.
- * @param location The location node where we want to check if the property is accessible.
- * @param isSuper True if the access is from `super.`.
- * @param writing True if this is a write property access, false if it is a read property access.
- * @param containingType The type of the object whose property is being accessed. (Not the type of the property.)
- * @param prop The symbol for the property being accessed.
- * @param errorNode The node where we should report an invalid property access error, or undefined if we should not report errors.
- */
- function checkPropertyAccessibilityAtLocation(location: Node,
- isSuper: boolean, writing: boolean,
- containingType: Type, prop: Symbol, errorNode?: Node): boolean {
-
- const flags = getDeclarationModifierFlagsFromSymbol(prop, writing);
-
- if (isSuper) {
- // TS 1.0 spec (April 2014): 4.8.2
- // - In a constructor, instance member function, instance member accessor, or
- // instance member variable initializer where this references a derived class instance,
- // a super property access is permitted and must specify a public instance member function of the base class.
- // - In a static member function or static member accessor
- // where this references the constructor function object of a derived class,
- // a super property access is permitted and must specify a public static member function of the base class.
- if (languageVersion < ScriptTarget.ES2015) {
- if (symbolHasNonMethodDeclaration(prop)) {
- if (errorNode) {
- error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword);
- }
- return false;
- }
- }
- if (flags & ModifierFlags.Abstract) {
- // A method cannot be accessed in a super property access if the method is abstract.
- // This error could mask a private property access error. But, a member
- // cannot simultaneously be private and abstract, so this will trigger an
- // additional error elsewhere.
- if (errorNode) {
- error(errorNode,
- Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression,
- symbolToString(prop),
- typeToString(getDeclaringClass(prop)!));
- }
- return false;
- }
- }
-
- // Referencing abstract properties within their own constructors is not allowed
- if ((flags & ModifierFlags.Abstract) && symbolHasNonMethodDeclaration(prop) &&
- (isThisProperty(location) || isThisInitializedObjectBindingExpression(location) || isObjectBindingPattern(location.parent) && isThisInitializedDeclaration(location.parent.parent))) {
- const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!);
- if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(location)) {
- if (errorNode) {
- error(errorNode,
- Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor,
- symbolToString(prop),
- getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!));
- }
- return false;
- }
- }
-
- // Public properties are otherwise accessible.
- if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) {
- return true;
- }
-
- // Property is known to be private or protected at this point
-
- // Private property is accessible if the property is within the declaring class
- if (flags & ModifierFlags.Private) {
- const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!;
- if (!isNodeWithinClass(location, declaringClassDeclaration)) {
- if (errorNode) {
- error(errorNode,
- Diagnostics.Property_0_is_private_and_only_accessible_within_class_1,
- symbolToString(prop),
- typeToString(getDeclaringClass(prop)!));
- }
- return false;
- }
- return true;
- }
-
- // Property is known to be protected at this point
-
- // All protected properties of a supertype are accessible in a super access
- if (isSuper) {
- return true;
- }
-
- // Find the first enclosing class that has the declaring classes of the protected constituents
- // of the property as base classes
- let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => {
- const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!) as InterfaceType;
- return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing) ? enclosingClass : undefined;
- });
- // A protected property is accessible if the property is within the declaring class or classes derived from it
- if (!enclosingClass) {
- // allow PropertyAccessibility if context is in function with this parameter
- // static member access is disallow
- let thisParameter: ParameterDeclaration | undefined;
- if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(location)) || !thisParameter.type) {
- if (errorNode) {
- error(errorNode,
- Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses,
- symbolToString(prop),
- typeToString(getDeclaringClass(prop) || containingType));
- }
- return false;
- }
-
- const thisType = getTypeFromTypeNode(thisParameter.type);
- enclosingClass = (((thisType.flags & TypeFlags.TypeParameter) ? getConstraintOfTypeParameter(thisType as TypeParameter) : thisType) as TypeReference).target;
- }
- // No further restrictions for static properties
- if (flags & ModifierFlags.Static) {
- return true;
- }
- if (containingType.flags & TypeFlags.TypeParameter) {
- // get the original type -- represented as the type constraint of the 'this' type
- containingType = (containingType as TypeParameter).isThisType ? getConstraintOfTypeParameter(containingType as TypeParameter)! : getBaseConstraintOfType(containingType as TypeParameter)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined
- }
- if (!containingType || !hasBaseType(containingType, enclosingClass)) {
- if (errorNode) {
- error(errorNode,
- Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1_This_is_an_instance_of_class_2,
- symbolToString(prop), typeToString(enclosingClass), typeToString(containingType));
- }
- return false;
- }
- return true;
- }
-
- function getThisParameterFromNodeContext(node: Node) {
- const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false);
- return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined;
- }
-
- function symbolHasNonMethodDeclaration(symbol: Symbol) {
- return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method));
- }
-
- function checkNonNullExpression(node: Expression | QualifiedName) {
- return checkNonNullType(checkExpression(node), node);
- }
-
- function isNullableType(type: Type) {
- return !!((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable);
- }
-
- function getNonNullableTypeIfNeeded(type: Type) {
- return isNullableType(type) ? getNonNullableType(type) : type;
- }
-
- function reportObjectPossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) {
- error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ?
- Diagnostics.Object_is_possibly_null_or_undefined :
- Diagnostics.Object_is_possibly_undefined :
- Diagnostics.Object_is_possibly_null
- );
- }
-
- function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) {
- error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ?
- Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined :
- Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined :
- Diagnostics.Cannot_invoke_an_object_which_is_possibly_null
- );
- }
-
- function checkNonNullTypeWithReporter(
- type: Type,
- node: Node,
- reportError: (node: Node, kind: TypeFlags) => void
- ): Type {
- if (strictNullChecks && type.flags & TypeFlags.Unknown) {
- error(node, Diagnostics.Object_is_of_type_unknown);
- return errorType;
- }
- const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable;
- if (kind) {
- reportError(node, kind);
- const t = getNonNullableType(type);
- return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t;
- }
- return type;
- }
-
- function checkNonNullType(type: Type, node: Node) {
- return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError);
- }
-
- function checkNonNullNonVoidType(type: Type, node: Node): Type {
- const nonNullType = checkNonNullType(type, node);
- if (nonNullType.flags & TypeFlags.Void) {
- error(node, Diagnostics.Object_is_possibly_undefined);
- }
- return nonNullType;
- }
-
- function checkPropertyAccessExpression(node: PropertyAccessExpression, checkMode: CheckMode | undefined) {
- return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain, checkMode) :
- checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name, checkMode);
- }
-
- function checkPropertyAccessChain(node: PropertyAccessChain, checkMode: CheckMode | undefined) {
- const leftType = checkExpression(node.expression);
- const nonOptionalType = getOptionalExpressionType(leftType, node.expression);
- return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name, checkMode), node, nonOptionalType !== leftType);
- }
-
- function checkQualifiedName(node: QualifiedName, checkMode: CheckMode | undefined) {
- const leftType = isPartOfTypeQuery(node) && isThisIdentifier(node.left) ? checkNonNullType(checkThisExpression(node.left), node.left) : checkNonNullExpression(node.left);
- return checkPropertyAccessExpressionOrQualifiedName(node, node.left, leftType, node.right, checkMode);
- }
-
- function isMethodAccessForCall(node: Node) {
- while (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
- node = node.parent;
- }
- return isCallOrNewExpression(node.parent) && node.parent.expression === node;
- }
-
- // Lookup the private identifier lexically.
- function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined {
- for (let containingClass = getContainingClass(location); !!containingClass; containingClass = getContainingClass(containingClass)) {
- const { symbol } = containingClass;
- const name = getSymbolNameForPrivateIdentifier(symbol, propName);
- const prop = (symbol.members && symbol.members.get(name)) || (symbol.exports && symbol.exports.get(name));
- if (prop) {
- return prop;
- }
- }
- }
-
- function checkGrammarPrivateIdentifierExpression(privId: PrivateIdentifier): boolean {
- if (!getContainingClass(privId)) {
- return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
- }
- if (!isExpressionNode(privId)) {
- return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression);
- }
- if (!getSymbolForPrivateIdentifierExpression(privId)) {
- return grammarErrorOnNode(privId, Diagnostics.Cannot_find_name_0, idText(privId));
- }
- return false;
- }
-
- function checkPrivateIdentifierExpression(privId: PrivateIdentifier): Type {
- checkGrammarPrivateIdentifierExpression(privId);
- const symbol = getSymbolForPrivateIdentifierExpression(privId);
- if (symbol) {
- markPropertyAsReferenced(symbol, /* nodeForCheckWriteOnly: */ undefined, /* isThisAccess: */ false);
- }
- return anyType;
- }
-
- function getSymbolForPrivateIdentifierExpression(privId: PrivateIdentifier): Symbol | undefined {
- if (!isExpressionNode(privId)) {
- return undefined;
- }
-
- const links = getNodeLinks(privId);
- if (links.resolvedSymbol === undefined) {
- links.resolvedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privId.escapedText, privId);
- }
- return links.resolvedSymbol;
- }
-
- function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined {
- return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName);
- }
-
- function checkPrivateIdentifierPropertyAccess(leftType: Type, right: PrivateIdentifier, lexicallyScopedIdentifier: Symbol | undefined): boolean {
- // Either the identifier could not be looked up in the lexical scope OR the lexically scoped identifier did not exist on the type.
- // Find a private identifier with the same description on the type.
- let propertyOnType: Symbol | undefined;
- const properties = getPropertiesOfType(leftType);
- if (properties) {
- forEach(properties, (symbol: Symbol) => {
- const decl = symbol.valueDeclaration;
- if (decl && isNamedDeclaration(decl) && isPrivateIdentifier(decl.name) && decl.name.escapedText === right.escapedText) {
- propertyOnType = symbol;
- return true;
- }
- });
- }
- const diagName = diagnosticName(right);
- if (propertyOnType) {
- const typeValueDecl = Debug.checkDefined(propertyOnType.valueDeclaration);
- const typeClass = Debug.checkDefined(getContainingClass(typeValueDecl));
- // We found a private identifier property with the same description.
- // Either:
- // - There is a lexically scoped private identifier AND it shadows the one we found on the type.
- // - It is an attempt to access the private identifier outside of the class.
- if (lexicallyScopedIdentifier?.valueDeclaration) {
- const lexicalValueDecl = lexicallyScopedIdentifier.valueDeclaration;
- const lexicalClass = getContainingClass(lexicalValueDecl);
- Debug.assert(!!lexicalClass);
- if (findAncestor(lexicalClass, n => typeClass === n)) {
- const diagnostic = error(
- right,
- Diagnostics.The_property_0_cannot_be_accessed_on_type_1_within_this_class_because_it_is_shadowed_by_another_private_identifier_with_the_same_spelling,
- diagName,
- typeToString(leftType)
- );
-
- addRelatedInfo(
- diagnostic,
- createDiagnosticForNode(
- lexicalValueDecl,
- Diagnostics.The_shadowing_declaration_of_0_is_defined_here,
- diagName
- ),
- createDiagnosticForNode(
- typeValueDecl,
- Diagnostics.The_declaration_of_0_that_you_probably_intended_to_use_is_defined_here,
- diagName
- )
- );
- return true;
- }
- }
- error(
- right,
- Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier,
- diagName,
- diagnosticName(typeClass.name || anon)
- );
- return true;
- }
- return false;
- }
-
- function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) {
- return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop))
- && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop);
- }
-
- function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined) {
- const parentSymbol = getNodeLinks(left).resolvedSymbol;
- const assignmentKind = getAssignmentTargetKind(node);
- const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType);
- const isAnyLike = isTypeAny(apparentType) || apparentType === silentNeverType;
- let prop: Symbol | undefined;
- if (isPrivateIdentifier(right)) {
- if (languageVersion < ScriptTarget.ESNext) {
- if (assignmentKind !== AssignmentKind.None) {
- checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldSet);
- }
- if (assignmentKind !== AssignmentKind.Definite) {
- checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet);
- }
- }
-
- const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right);
- if (assignmentKind && lexicallyScopedSymbol && lexicallyScopedSymbol.valueDeclaration && isMethodDeclaration(lexicallyScopedSymbol.valueDeclaration)) {
- grammarErrorOnNode(right, Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, idText(right));
- }
-
- if (lexicallyScopedSymbol?.valueDeclaration && (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && !useDefineForClassFields)) {
- const lexicalClass = getContainingClass(lexicallyScopedSymbol.valueDeclaration);
- const parentStaticFieldInitializer = findAncestor(node, (n) => {
- if (n === lexicalClass) return "quit";
- if (isPropertyDeclaration(n.parent) && hasStaticModifier(n.parent) && n.parent.initializer === n && n.parent.parent === lexicalClass) {
- return true;
- }
- return false;
- });
- if (parentStaticFieldInitializer) {
- const parentStaticFieldInitializerSymbol = getSymbolOfNode(parentStaticFieldInitializer.parent);
- Debug.assert(parentStaticFieldInitializerSymbol, "Initializer without declaration symbol");
- const diagnostic = error(node,
- Diagnostics.Property_0_may_not_be_used_in_a_static_property_s_initializer_in_the_same_class_when_target_is_esnext_and_useDefineForClassFields_is_false,
- symbolName(lexicallyScopedSymbol));
- addRelatedInfo(diagnostic,
- createDiagnosticForNode(parentStaticFieldInitializer.parent,
- Diagnostics.Initializer_for_property_0,
- symbolName(parentStaticFieldInitializerSymbol))
- );
- }
- }
-
- if (isAnyLike) {
- if (lexicallyScopedSymbol) {
- return isErrorType(apparentType) ? errorType : apparentType;
- }
- if (!getContainingClass(right)) {
- grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
- return anyType;
- }
- }
- prop = lexicallyScopedSymbol ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol) : undefined;
- // Check for private-identifier-specific shadowing and lexical-scoping errors.
- if (!prop && checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) {
- return errorType;
- }
- else {
- const isSetonlyAccessor = prop && prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
- if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) {
- error(node, Diagnostics.Private_accessor_was_defined_without_a_getter);
- }
- }
- }
- else {
- if (isAnyLike) {
- if (isIdentifier(left) && parentSymbol) {
- markAliasReferenced(parentSymbol, node);
- }
- return isErrorType(apparentType) ? errorType : apparentType;;
- }
- prop = getPropertyOfType(apparentType, right.escapedText);
- }
- // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums.
- // 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`.
- if (isIdentifier(left) && parentSymbol && (compilerOptions.isolatedModules || !(prop && isConstEnumOrConstEnumOnlyModule(prop)) || shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node))) {
- markAliasReferenced(parentSymbol, node);
- }
-
- let propType: Type;
- if (!prop) {
- const indexInfo = !isPrivateIdentifier(right) && (assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType)) ?
- getApplicableIndexInfoForName(apparentType, right.escapedText) : undefined;
- if (!(indexInfo && indexInfo.type)) {
- const isUncheckedJS = isUncheckedJSSuggestion(node, leftType.symbol, /*excludeClasses*/ true);
- if (!isUncheckedJS && isJSLiteralType(leftType)) {
- return anyType;
- }
- if (leftType.symbol === globalThisSymbol) {
- if (globalThisSymbol.exports!.has(right.escapedText) && (globalThisSymbol.exports!.get(right.escapedText)!.flags & SymbolFlags.BlockScoped)) {
- error(right, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(right.escapedText), typeToString(leftType));
- }
- else if (noImplicitAny) {
- error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType));
- }
- return anyType;
- }
- if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) {
- reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType, isUncheckedJS);
- }
- return errorType;
- }
- if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) {
- error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType));
- }
-
- propType = (compilerOptions.noUncheckedIndexedAccess && !isAssignmentTarget(node)) ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type;
- if (compilerOptions.noPropertyAccessFromIndexSignature && isPropertyAccessExpression(node)) {
- error(right, Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0, unescapeLeadingUnderscores(right.escapedText));
- }
- }
- else {
- if (prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) {
- addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string);
- }
- checkPropertyNotUsedBeforeDeclaration(prop, node, right);
- markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol));
- getNodeLinks(node).resolvedSymbol = prop;
- const writing = isWriteAccess(node);
- checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, writing, apparentType, prop);
- if (isAssignmentToReadonlyEntity(node as Expression, prop, assignmentKind)) {
- error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right));
- return errorType;
- }
-
- propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writing ? getSetAccessorTypeOfSymbol(prop) : getTypeOfSymbol(prop);
- }
-
- return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode);
- }
-
- /**
- * Determines whether a did-you-mean error should be a suggestion in an unchecked JS file.
- * Only applies to unchecked JS files without checkJS, // @ts-check or // @ts-nocheck
- * It does not suggest when the suggestion:
- * - Is from a global file that is different from the reference file, or
- * - (optionally) Is a class, or is a this.x property access expression
- */
- function isUncheckedJSSuggestion(node: Node | undefined, suggestion: Symbol | undefined, excludeClasses: boolean): boolean {
- const file = getSourceFileOfNode(node);
- if (file) {
- if (compilerOptions.checkJs === undefined && file.checkJsDirective === undefined && (file.scriptKind === ScriptKind.JS || file.scriptKind === ScriptKind.JSX)) {
- const declarationFile = forEach(suggestion?.declarations, getSourceFileOfNode);
- return !(file !== declarationFile && !!declarationFile && isGlobalSourceFile(declarationFile))
- && !(excludeClasses && suggestion && suggestion.flags & SymbolFlags.Class)
- && !(!!node && excludeClasses && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword);
- }
- }
- return false;
- }
-
- function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node, checkMode: CheckMode | undefined) {
- // Only compute control flow type if this is a property access expression that isn't an
- // assignment target, and the referenced property was declared as a variable, property,
- // accessor, or optional method.
- const assignmentKind = getAssignmentTargetKind(node);
- if (assignmentKind === AssignmentKind.Definite) {
- return removeMissingType(propType, !!(prop && prop.flags & SymbolFlags.Optional));
- }
- if (prop &&
- !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor))
- && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)
- && !isDuplicatedCommonJSExport(prop.declarations)) {
- return propType;
- }
- if (propType === autoType) {
- return getFlowTypeOfProperty(node, prop);
- }
- propType = getNarrowableTypeForReference(propType, node, checkMode);
- // If strict null checks and strict property initialization checks are enabled, if we have
- // a this.xxx property access, if the property is an instance property without an initializer,
- // and if we are in a constructor of the same class as the property declaration, assume that
- // the property is uninitialized at the top of the control flow.
- let assumeUninitialized = false;
- if (strictNullChecks && strictPropertyInitialization && isAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword) {
- const declaration = prop && prop.valueDeclaration;
- if (declaration && isPropertyWithoutInitializer(declaration)) {
- if (!isStatic(declaration)) {
- const flowContainer = getControlFlowContainer(node);
- if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent && !(declaration.flags & NodeFlags.Ambient)) {
- assumeUninitialized = true;
- }
- }
- }
- }
- else if (strictNullChecks && prop && prop.valueDeclaration &&
- isPropertyAccessExpression(prop.valueDeclaration) &&
- getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration) &&
- getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) {
- assumeUninitialized = true;
- }
- const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType);
- if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
- error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217
- // Return the declared type to reduce follow-on errors
- return propType;
- }
- return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
- }
-
- function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier | PrivateIdentifier): void {
- const { valueDeclaration } = prop;
- if (!valueDeclaration || getSourceFileOfNode(node).isDeclarationFile) {
- return;
- }
-
- let diagnosticMessage;
- const declarationName = idText(right);
- if (isInPropertyInitializerOrClassStaticBlock(node)
- && !isOptionalPropertyDeclaration(valueDeclaration)
- && !(isAccessExpression(node) && isAccessExpression(node.expression))
- && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)
- && (compilerOptions.useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) {
- diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName);
- }
- else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration &&
- node.parent.kind !== SyntaxKind.TypeReference &&
- !(valueDeclaration.flags & NodeFlags.Ambient) &&
- !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) {
- diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName);
- }
-
- if (diagnosticMessage) {
- addRelatedInfo(diagnosticMessage,
- createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName)
- );
- }
- }
-
- function isInPropertyInitializerOrClassStaticBlock(node: Node): boolean {
- return !!findAncestor(node, node => {
- switch (node.kind) {
- case SyntaxKind.PropertyDeclaration:
- return true;
- case SyntaxKind.PropertyAssignment:
- case SyntaxKind.MethodDeclaration:
- case SyntaxKind.GetAccessor:
- case SyntaxKind.SetAccessor:
- case SyntaxKind.SpreadAssignment:
- case SyntaxKind.ComputedPropertyName:
- case SyntaxKind.TemplateSpan:
- case SyntaxKind.JsxExpression:
- case SyntaxKind.JsxAttribute:
- case SyntaxKind.JsxAttributes:
- case SyntaxKind.JsxSpreadAttribute:
- case SyntaxKind.JsxOpeningElement:
- case SyntaxKind.ExpressionWithTypeArguments:
- case SyntaxKind.HeritageClause:
- return false;
- case SyntaxKind.ArrowFunction:
- case SyntaxKind.ExpressionStatement:
- return isBlock(node.parent) && isClassStaticBlockDeclaration(node.parent.parent) ? true : "quit";
- default:
- return isExpressionNode(node) ? false : "quit";
- }
- });
- }
-
- /**
- * It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass.
- * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration.
- */
- function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean {
- if (!(prop.parent!.flags & SymbolFlags.Class)) {
- return false;
- }
- let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType;
- while (true) {
- classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined;
- if (!classType) {
- return false;
- }
- const superProperty = getPropertyOfType(classType, prop.escapedName);
- if (superProperty && superProperty.valueDeclaration) {
- return true;
- }
- }
- }
-
- function getSuperClass(classType: InterfaceType): Type | undefined {
- const x = getBaseTypes(classType);
- if (x.length === 0) {
- return undefined;
- }
- return getIntersectionType(x);
- }
-
- function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type, isUncheckedJS: boolean) {
- let errorInfo: DiagnosticMessageChain | undefined;
- let relatedInfo: Diagnostic | undefined;
- if (!isPrivateIdentifier(propNode) && containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) {
- for (const subtype of (containingType as UnionType).types) {
- if (!getPropertyOfType(subtype, propNode.escapedText) && !getApplicableIndexInfoForName(subtype, propNode.escapedText)) {
- errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype));
- break;
- }
- }
- }
- if (typeHasStaticProperty(propNode.escapedText, containingType)) {
- const propName = declarationNameToString(propNode);
- const typeName = typeToString(containingType);
- errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName, typeName, typeName + "." + propName);
- }
- else {
- const promisedType = getPromisedTypeOfPromise(containingType);
- if (promisedType && getPropertyOfType(promisedType, propNode.escapedText)) {
- errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
- relatedInfo = createDiagnosticForNode(propNode, Diagnostics.Did_you_forget_to_use_await);
- }
- else {
- const missingProperty = declarationNameToString(propNode);
- const container = typeToString(containingType);
- const libSuggestion = getSuggestedLibForNonExistentProperty(missingProperty, containingType);
- if (libSuggestion !== undefined) {
- errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_2_or_later, missingProperty, container, libSuggestion);
- }
- else {
- const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType);
- if (suggestion !== undefined) {
- const suggestedName = symbolName(suggestion);
- const message = isUncheckedJS ? Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2 : Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2;
- errorInfo = chainDiagnosticMessages(errorInfo, message, missingProperty, container, suggestedName);
- relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName);
- }
- else {
- const diagnostic = containerSeemsToBeEmptyDomElement(containingType)
- ? Diagnostics.Property_0_does_not_exist_on_type_1_Try_changing_the_lib_compiler_option_to_include_dom
- : Diagnostics.Property_0_does_not_exist_on_type_1;
- errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), diagnostic, missingProperty, container);
- }
- }
- }
- }
- const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo);
- if (relatedInfo) {
- addRelatedInfo(resultDiagnostic, relatedInfo);
- }
- addErrorOrSuggestion(!isUncheckedJS || errorInfo.code !== Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2.code, resultDiagnostic);
- }
-
- function containerSeemsToBeEmptyDomElement(containingType: Type) {
- return (compilerOptions.lib && !compilerOptions.lib.includes("dom")) &&
- everyContainedType(containingType, type => type.symbol && /^(EventTarget|Node|((HTML[a-zA-Z]*)?Element))$/.test(unescapeLeadingUnderscores(type.symbol.escapedName))) &&
- isEmptyObjectType(containingType);
- }
-
- function typeHasStaticProperty(propName: __String, containingType: Type): boolean {
- const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName);
- return prop !== undefined && !!prop.valueDeclaration && isStatic(prop.valueDeclaration);
- }
-
- function getSuggestedLibForNonExistentName(name: __String | Identifier) {
- const missingName = diagnosticName(name);
- const allFeatures = getScriptTargetFeatures();
- const libTargets = getOwnKeys(allFeatures);
- for (const libTarget of libTargets) {
- const containingTypes = getOwnKeys(allFeatures[libTarget]);
- if (containingTypes !== undefined && contains(containingTypes, missingName)) {
- return libTarget;
- }
- }
- }
-
- function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) {
- const container = getApparentType(containingType).symbol;
- if (!container) {
- return undefined;
- }
- const allFeatures = getScriptTargetFeatures();
- const libTargets = getOwnKeys(allFeatures);
- for (const libTarget of libTargets) {
- const featuresOfLib = allFeatures[libTarget];
- const featuresOfContainingType = featuresOfLib[symbolName(container)];
- if (featuresOfContainingType !== undefined && contains(featuresOfContainingType, missingProperty)) {
- return libTarget;
- }
- }
- }
-
- function getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined {
- return getSpellingSuggestionForName(name, getPropertiesOfType(baseType), SymbolFlags.ClassMember);
- }
-
- function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined {
- let props = getPropertiesOfType(containingType);
- if (typeof name !== "string") {
- const parent = name.parent;
- if (isPropertyAccessExpression(parent)) {
- props = filter(props, prop => isValidPropertyAccessForCompletions(parent, containingType, prop));
- }
- name = idText(name);
- }
- return getSpellingSuggestionForName(name, props, SymbolFlags.Value);
- }
-
- function getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined {
- const strName = isString(name) ? name : idText(name);
- const properties = getPropertiesOfType(containingType);
- const jsxSpecific = strName === "for" ? find(properties, x => symbolName(x) === "htmlFor")
- : strName === "class" ? find(properties, x => symbolName(x) === "className")
- : undefined;
- return jsxSpecific ?? getSpellingSuggestionForName(strName, properties, SymbolFlags.Value);
- }
-
- function getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined {
- const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType);
- return suggestion && symbolName(suggestion);
- }
-
- function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined {
- Debug.assert(outerName !== undefined, "outername should always be defined");
- const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => {
- Debug.assertEqual(outerName, name, "name should equal outerName");
- const symbol = getSymbol(symbols, name, meaning);
- // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function
- // So the table *contains* `x` but `x` isn't actually in scope.
- // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion.
- if (symbol) return symbol;
- let candidates: Symbol[];
- if (symbols === globals) {
- const primitives = mapDefined(
- ["string", "number", "boolean", "object", "bigint", "symbol"],
- s => symbols.has((s.charAt(0).toUpperCase() + s.slice(1)) as __String)
- ? createSymbol(SymbolFlags.TypeAlias, s as __String) as Symbol
- : undefined);
- candidates = primitives.concat(arrayFrom(symbols.values()));
- }
- else {
- candidates = arrayFrom(symbols.values());
- }
- return getSpellingSuggestionForName(unescapeLeadingUnderscores(name), candidates, meaning);
- });
- return result;
- }
-
- function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined {
- const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning);
- return symbolResult && symbolName(symbolResult);
- }
-
- function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined {
- return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember);
- }
-
- function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined {
- const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule);
- return suggestion && symbolName(suggestion);
- }
-
- function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined {
- // check if object type has setter or getter
- function hasProp(name: "set" | "get") {
- const prop = getPropertyOfObjectType(objectType, name as __String);
- if (prop) {
- const s = getSingleCallSignature(getTypeOfSymbol(prop));
- return !!s && getMinArgumentCount(s) >= 1 && isTypeAssignableTo(keyedType, getTypeAtPosition(s, 0));
- }
- return false;
- };
-
- const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get";
- if (!hasProp(suggestedMethod)) {
- return undefined;
- }
-
- let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression);
- if (suggestion === undefined) {
- suggestion = suggestedMethod;
- }
- else {
- suggestion += "." + suggestedMethod;
- }
-
- return suggestion;
- }
-
- function getSuggestedTypeForNonexistentStringLiteralType(source: StringLiteralType, target: UnionType): StringLiteralType | undefined {
- const candidates = target.types.filter((type): type is StringLiteralType => !!(type.flags & TypeFlags.StringLiteral));
- return getSpellingSuggestion(source.value, candidates, type => type.value);
- }
-
- /**
- * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
- * Names less than length 3 only check for case-insensitive equality, not levenshtein distance.
- *
- * If there is a candidate that's the same except for case, return that.
- * If there is a candidate that's within one edit of the name, return that.
- * Otherwise, return the candidate with the smallest Levenshtein distance,
- * except for candidates:
- * * With no name
- * * Whose meaning doesn't match the `meaning` parameter.
- * * Whose length differs from the target name by more than 0.34 of the length of the name.
- * * Whose levenshtein distance is more than 0.4 of the length of the name
- * (0.4 allows 1 substitution/transposition for every 5 characters,
- * and 1 insertion/deletion at 3 characters)
- */
- function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined {
- return getSpellingSuggestion(name, symbols, getCandidateName);
-
- function getCandidateName(candidate: Symbol) {
- const candidateName = symbolName(candidate);
- if (startsWith(candidateName, "\"")) {
- return undefined;
- }
-
- if (candidate.flags & meaning) {
- return candidateName;
- }
-
- if (candidate.flags & SymbolFlags.Alias) {
- const alias = tryResolveAlias(candidate);
- if (alias && alias.flags & meaning) {
- return candidateName;
- }
- }
-
- return undefined;
- }
- }
-
- function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isSelfTypeAccess: boolean) {
- const valueDeclaration = prop && (prop.flags & SymbolFlags.ClassMember) && prop.valueDeclaration;
- if (!valueDeclaration) {
- return;
- }
- const hasPrivateModifier = hasEffectiveModifier(valueDeclaration, ModifierFlags.Private);
- const hasPrivateIdentifier = prop.valueDeclaration && isNamedDeclaration(prop.valueDeclaration) && isPrivateIdentifier(prop.valueDeclaration.name);
- if (!hasPrivateModifier && !hasPrivateIdentifier) {
- return;
- }
- if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor)) {
- return;
- }
- if (isSelfTypeAccess) {
- // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters).
- const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration);
- if (containingMethod && containingMethod.symbol === prop) {
- return;
- }
- }
-
- (getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All;
- }
-
- function isSelfTypeAccess(name: Expression | QualifiedName, parent: Symbol | undefined) {
- return name.kind === SyntaxKind.ThisKeyword
- || !!parent && isEntityNameExpression(name) && parent === getResolvedSymbol(getFirstIdentifier(name));
- }
-
- function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean {
- switch (node.kind) {
- case SyntaxKind.PropertyAccessExpression:
- return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression)));
- case SyntaxKind.QualifiedName:
- return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left)));
- case SyntaxKind.ImportType:
- return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node));
- }
- }
-
- /**
- * Checks if an existing property access is valid for completions purposes.
- * @param node a property access-like node where we want to check if we can access a property.
- * This node does not need to be an access of the property we are checking.
- * e.g. in completions, this node will often be an incomplete property access node, as in `foo.`.
- * Besides providing a location (i.e. scope) used to check property accessibility, we use this node for
- * computing whether this is a `super` property access.
- * @param type the type whose property we are checking.
- * @param property the accessed property's symbol.
- */
- function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean {
- return isPropertyAccessible(node,
- node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword,
- /* isWrite */ false,
- type,
- property);
- // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context.
- }
-
- function isValidPropertyAccessWithType(
- node: PropertyAccessExpression | QualifiedName | ImportTypeNode,
- isSuper: boolean,
- propertyName: __String,
- type: Type): boolean {
-
- // Short-circuiting for improved performance.
- if (isTypeAny(type)) {
- return true;
- }
-
- const prop = getPropertyOfType(type, propertyName);
- return !!prop && isPropertyAccessible(node, isSuper, /* isWrite */ false, type, prop);
- }
-
- /**
- * Checks if a property can be accessed in a location.
- * The location is given by the `node` parameter.
- * The node does not need to be a property access.
- * @param node location where to check property accessibility
- * @param isSuper whether to consider this a `super` property access, e.g. `super.foo`.
- * @param isWrite whether this is a write access, e.g. `++foo.x`.
- * @param containingType type where the property comes from.
- * @param property property symbol.
- */
- function isPropertyAccessible(
- node: Node,
- isSuper: boolean,
- isWrite: boolean,
- containingType: Type,
- property: Symbol): boolean {
-
- // Short-circuiting for improved performance.
- if (isTypeAny(containingType)) {
- return true;
- }
-
- // A #private property access in an optional chain is an error dealt with by the parser.
- // The checker does not check for it, so we need to do our own check here.
- if (property.valueDeclaration && isPrivateIdentifierClassElementDeclaration(property.valueDeclaration)) {
- const declClass = getContainingClass(property.valueDeclaration);
- return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass);
- }
-
- return checkPropertyAccessibilityAtLocation(node, isSuper, isWrite, containingType, property);
- }
-
- /**
- * Return the symbol of the for-in variable declared or referenced by the given for-in statement.
- */
- function getForInVariableSymbol(node: ForInStatement): Symbol | undefined {
- const initializer = node.initializer;
- if (initializer.kind === SyntaxKind.VariableDeclarationList) {
- const variable = (initializer as VariableDeclarationList).declarations[0];
- if (variable && !isBindingPattern(variable.name)) {
- return getSymbolOfNode(variable);
- }
- }
- else if (initializer.kind === SyntaxKind.Identifier) {
- return getResolvedSymbol(initializer as Identifier);
- }
- return undefined;
- }
-
- /**
- * Return true if the given type is considered to have numeric property names.
- */
- function hasNumericPropertyNames(type: Type) {
- return getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, numberType);
- }
-
- /**
- * Return true if given node is an expression consisting of an identifier (possibly parenthesized)
- * that references a for-in variable for an object with numeric property names.
- */
- function isForInVariableForNumericPropertyNames(expr: Expression) {
- const e = skipParentheses(expr);
- if (e.kind === SyntaxKind.Identifier) {
- const symbol = getResolvedSymbol(e as Identifier);
- if (symbol.flags & SymbolFlags.Variable) {
- let child: Node = expr;
- let node = expr.parent;
- while (node) {
- if (node.kind === SyntaxKind.ForInStatement &&
- child === (node as ForInStatement).statement &&
- getForInVariableSymbol(node as ForInStatement) === symbol &&
- hasNumericPropertyNames(getTypeOfExpression((node as ForInStatement).expression))) {
- return true;
- }
- child = node;
- node = node.parent;
- }
- }
- }
- return false;
- }
-
- function checkIndexedAccess(node: ElementAccessExpression, checkMode: CheckMode | undefined): Type {
- return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain, checkMode) :
- checkElementAccessExpression(node, checkNonNullExpression(node.expression), checkMode);
- }
-
- function checkElementAccessChain(node: ElementAccessChain, checkMode: CheckMode | undefined) {
- const exprType = checkExpression(node.expression);
- const nonOptionalType = getOptionalExpressionType(exprType, node.expression);
- return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression), checkMode), node, nonOptionalType !== exprType);
- }
-
- function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type, checkMode: CheckMode | undefined): Type {
- const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType;
- const indexExpression = node.argumentExpression;
- const indexType = checkExpression(indexExpression);
-
- if (isErrorType(objectType) || objectType === silentNeverType) {
- return objectType;
- }
-
- if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) {
- error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
- return errorType;
- }
-
- const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType;
- const accessFlags = isAssignmentTarget(node) ?
- AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) :
- AccessFlags.ExpressionPosition;
- const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType;
- return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node);
- }
-
- function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement {
- return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node);
- }
-
- function resolveUntypedCall(node: CallLikeExpression): Signature {
- if (callLikeExpressionMayHaveTypeArguments(node)) {
- // Check type arguments even though we will give an error that untyped calls may not accept type arguments.
- // This gets us diagnostics for the type arguments and marks them as referenced.
- forEach(node.typeArguments, checkSourceElement);
- }
-
- if (node.kind === SyntaxKind.TaggedTemplateExpression) {
- checkExpression(node.template);
- }
- else if (isJsxOpeningLikeElement(node)) {
- checkExpression(node.attributes);
- }
- else if (node.kind !== SyntaxKind.Decorator) {
- forEach((node as CallExpression).arguments, argument => {
- checkExpression(argument);
- });
- }
- return anySignature;
- }
-
- function resolveErrorCall(node: CallLikeExpression): Signature {
- resolveUntypedCall(node);
- return unknownSignature;
- }
-
- // Re-order candidate signatures into the result array. Assumes the result array to be empty.
- // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order
- // A nit here is that we reorder only signatures that belong to the same symbol,
- // so order how inherited signatures are processed is still preserved.
- // interface A { (x: string): void }
- // interface B extends A { (x: 'foo'): string }
- // const b: B;
- // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
- function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void {
- let lastParent: Node | undefined;
- let lastSymbol: Symbol | undefined;
- let cutoffIndex = 0;
- let index: number | undefined;
- let specializedIndex = -1;
- let spliceIndex: number;
- Debug.assert(!result.length);
- for (const signature of signatures) {
- const symbol = signature.declaration && getSymbolOfNode(signature.declaration);
- const parent = signature.declaration && signature.declaration.parent;
- if (!lastSymbol || symbol === lastSymbol) {
- if (lastParent && parent === lastParent) {
- index = index! + 1;
- }
- else {
- lastParent = parent;
- index = cutoffIndex;
- }
- }
- else {
- // current declaration belongs to a different symbol
- // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex
- index = cutoffIndex = result.length;
- lastParent = parent;
- }
- lastSymbol = symbol;
-
- // specialized signatures always need to be placed before non-specialized signatures regardless
- // of the cutoff position; see GH#1133
- if (signatureHasLiteralTypes(signature)) {
- specializedIndex++;
- spliceIndex = specializedIndex;
- // The cutoff index always needs to be greater than or equal to the specialized signature index
- // in order to prevent non-specialized signatures from being added before a specialized
- // signature.
- cutoffIndex++;
- }
- else {
- spliceIndex = index;
- }
-
- result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature);
- }
- }
-
- function isSpreadArgument(arg: Expression | undefined): arg is Expression {
- return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).isSpread);
- }
-
- function getSpreadArgumentIndex(args: readonly Expression[]): number {
- return findIndex(args, isSpreadArgument);
- }
-
- function acceptsVoid(t: Type): boolean {
- return !!(t.flags & TypeFlags.Void);
- }
-
- function acceptsVoidUndefinedUnknownOrAny(t: Type): boolean {
- return !!(t.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Unknown | TypeFlags.Any));
- }
-
- function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: Signature, signatureHelpTrailingComma = false) {
- let argCount: number;
- let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments
- let effectiveParameterCount = getParameterCount(signature);
- let effectiveMinimumArguments = getMinArgumentCount(signature);
-
- if (node.kind === SyntaxKind.TaggedTemplateExpression) {
- argCount = args.length;
- if (node.template.kind === SyntaxKind.TemplateExpression) {
- // If a tagged template expression lacks a tail literal, the call is incomplete.
- // Specifically, a template only can end in a TemplateTail or a Missing literal.
- const lastSpan = last(node.template.templateSpans); // we should always have at least one span.
- callIsIncomplete = nodeIsMissing(lastSpan.literal) || !!lastSpan.literal.isUnterminated;
- }
- else {
- // If the template didn't end in a backtick, or its beginning occurred right prior to EOF,
- // then this might actually turn out to be a TemplateHead in the future;
- // so we consider the call to be incomplete.
- const templateLiteral = node.template as LiteralExpression;
- Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral);
- callIsIncomplete = !!templateLiteral.isUnterminated;
- }
- }
- else if (node.kind === SyntaxKind.Decorator) {
- argCount = getDecoratorArgumentCount(node, signature);
- }
- else if (isJsxOpeningLikeElement(node)) {
- callIsIncomplete = node.attributes.end === node.end;
- if (callIsIncomplete) {
- return true;
- }
- argCount = effectiveMinimumArguments === 0 ? args.length : 1;
- effectiveParameterCount = args.length === 0 ? effectiveParameterCount : 1; // class may have argumentless ctor functions - still resolve ctor and compare vs props member type
- effectiveMinimumArguments = Math.min(effectiveMinimumArguments, 1); // sfc may specify context argument - handled by framework and not typechecked
- }
- else if (!node.arguments) {
- // This only happens when we have something of the form: 'new C'
- Debug.assert(node.kind === SyntaxKind.NewExpression);
- return getMinArgumentCount(signature) === 0;
- }
- else {
- argCount = signatureHelpTrailingComma ? args.length + 1 : args.length;
-
- // If we are missing the close parenthesis, the call is incomplete.
- callIsIncomplete = node.arguments.end === node.end;
-
- // If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range.
- const spreadArgIndex = getSpreadArgumentIndex(args);
- if (spreadArgIndex >= 0) {
- return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature));
- }
- }
-
- // Too many arguments implies incorrect arity.
- if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) {
- return false;
- }
-
- // If the call is incomplete, we should skip the lower bound check.
- // JSX signatures can have extra parameters provided by the library which we don't check
- if (callIsIncomplete || argCount >= effectiveMinimumArguments) {
- return true;
- }
- for (let i = argCount; i < effectiveMinimumArguments; i++) {
- const type = getTypeAtPosition(signature, i);
- if (filterType(type, isInJSFile(node) && !strictNullChecks ? acceptsVoidUndefinedUnknownOrAny : acceptsVoid).flags & TypeFlags.Never) {
- return false;
- }
- }
- return true;
- }
-
- function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray | undefined) {
- // If the user supplied type arguments, but the number of type arguments does not match
- // the declared number of type parameters, the call has an incorrect arity.
- const numTypeParameters = length(signature.typeParameters);
- const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters);
- return !some(typeArguments) ||
- (typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters);
- }
-
- // If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
- function getSingleCallSignature(type: Type): Signature | undefined {
- return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false);
- }
-
- function getSingleCallOrConstructSignature(type: Type): Signature | undefined {
- return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) ||
- getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false);
- }
-
- function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined {
- if (type.flags & TypeFlags.Object) {
- const resolved = resolveStructuredTypeMembers(type as ObjectType);
- if (allowMembers || resolved.properties.length === 0 && resolved.indexInfos.length === 0) {
- if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) {
- return resolved.callSignatures[0];
- }
- if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) {
- return resolved.constructSignatures[0];
- }
- }
- }
- return undefined;
- }
-
- // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec)
- function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): Signature {
- const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes);
- // We clone the inferenceContext to avoid fixing. For example, when the source signature is (x: T) => T[] and
- // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any')
- // for T but leave it possible to later infer '[any]' back to A.
- const restType = getEffectiveRestType(contextualSignature);
- const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper);
- const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature;
- applyToParameterTypes(sourceSignature, signature, (source, target) => {
- // Type parameters from outer context referenced by source type are fixed by instantiation of the source type
- inferTypes(context.inferences, source, target);
- });
- if (!inferenceContext) {
- applyToReturnTypes(contextualSignature, signature, (source, target) => {
- inferTypes(context.inferences, source, target, InferencePriority.ReturnType);
- });
- }
- return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration));
- }
-
- function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, checkMode: CheckMode, context: InferenceContext): Type[] {
- const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node);
- const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode);
- inferTypes(context.inferences, checkAttrType, paramType);
- return getInferredTypes(context);
- }
-
- function getThisArgumentType(thisArgumentNode: LeftHandSideExpression | undefined) {
- if (!thisArgumentNode) {
- return voidType;
- }
- const thisArgumentType = checkExpression(thisArgumentNode);
- return isOptionalChainRoot(thisArgumentNode.parent) ? getNonNullableType(thisArgumentType) :
- isOptionalChain(thisArgumentNode.parent) ? removeOptionalTypeMarker(thisArgumentType) :
- thisArgumentType;
- }
-
- function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] {
- if (isJsxOpeningLikeElement(node)) {
- return inferJsxTypeArguments(node, signature, checkMode, context);
- }
-
- // If a contextual type is available, infer from that type to the return type of the call expression. For
- // example, given a 'function wrap