import { __String, AccessExpression, AccessFlags, AccessorDeclaration, addRange, addRelatedInfo, addSyntheticLeadingComment, addSyntheticTrailingComment, AliasDeclarationNode, AllAccessorDeclarations, AmbientModuleDeclaration, and, AnonymousType, AnyImportOrJsDocImport, AnyImportOrReExport, append, appendIfUnique, ArrayBindingPattern, arrayFrom, arrayIsEqualTo, arrayIsHomogeneous, ArrayLiteralExpression, arrayOf, arrayToMultiMap, ArrayTypeNode, ArrowFunction, AsExpression, AssertionExpression, AssignmentDeclarationKind, AssignmentKind, AssignmentPattern, AwaitExpression, BaseType, BigIntLiteral, BigIntLiteralType, BinaryExpression, BinaryOperator, BinaryOperatorToken, binarySearch, BindableObjectDefinePropertyCall, BindableStaticNameExpression, BindingElement, BindingElementGrandparent, BindingName, BindingPattern, bindSourceFile, Block, BooleanLiteral, BreakOrContinueStatement, CallChain, CallExpression, CallLikeExpression, CallSignatureDeclaration, CancellationToken, canHaveDecorators, canHaveExportModifier, canHaveFlowNode, canHaveIllegalDecorators, canHaveIllegalModifiers, canHaveJSDoc, canHaveLocals, canHaveModifiers, canHaveModuleSpecifier, canHaveSymbol, canIncludeBindAndCheckDiagnostics, canUsePropertyAccess, cartesianProduct, CaseBlock, CaseClause, CaseOrDefaultClause, cast, chainDiagnosticMessages, CharacterCodes, CheckFlags, ClassDeclaration, ClassElement, classElementOrClassElementParameterIsDecorated, ClassExpression, ClassLikeDeclaration, classOrConstructorParameterIsDecorated, ClassStaticBlockDeclaration, clear, compareDiagnostics, comparePaths, compareValues, Comparison, CompilerOptions, ComputedPropertyName, concatenate, concatenateDiagnosticMessageChains, ConditionalExpression, ConditionalRoot, ConditionalType, ConditionalTypeNode, ConstructorDeclaration, ConstructorTypeNode, ConstructSignatureDeclaration, contains, containsParseError, ContextFlags, copyEntries, countWhere, createBinaryExpressionTrampoline, createCompilerDiagnostic, createDetachedDiagnostic, createDiagnosticCollection, createDiagnosticForFileFromMessageChain, createDiagnosticForNode, createDiagnosticForNodeArray, createDiagnosticForNodeArrayFromMessageChain, createDiagnosticForNodeFromMessageChain, createDiagnosticMessageChainFromDiagnostic, createEmptyExports, createEvaluator, createFileDiagnostic, createFlowNode, createGetSymbolWalker, createModeAwareCacheKey, createModeMismatchDetails, createModuleNotFoundChain, createMultiMap, createNameResolver, createPrinterWithDefaults, createPrinterWithRemoveComments, createPrinterWithRemoveCommentsNeverAsciiEscape, createPrinterWithRemoveCommentsOmitTrailingSemicolon, createPropertyNameNodeForIdentifierOrLiteral, createScanner, createSymbolTable, createSyntacticTypeNodeBuilder, createTextWriter, Debug, Declaration, DeclarationName, declarationNameToString, DeclarationStatement, DeclarationWithTypeParameterChildren, DeclarationWithTypeParameters, Decorator, deduplicate, DefaultClause, defaultMaximumTruncationLength, DeferredTypeReference, DeleteExpression, Diagnostic, DiagnosticAndArguments, DiagnosticArguments, DiagnosticCategory, DiagnosticMessage, DiagnosticMessageChain, DiagnosticRelatedInformation, Diagnostics, DiagnosticWithLocation, DoStatement, DynamicNamedDeclaration, ElementAccessChain, ElementAccessExpression, ElementFlags, ElementWithComputedPropertyName, EmitFlags, EmitHint, emitModuleKindIsNonNodeESM, EmitResolver, EmitTextWriter, emptyArray, EntityName, EntityNameExpression, EntityNameOrEntityNameExpression, entityNameToString, EnumDeclaration, EnumMember, EnumType, equateValues, ErrorOutputContainer, escapeLeadingUnderscores, escapeString, EvaluatorResult, evaluatorResult, every, EvolvingArrayType, ExclamationToken, ExportAssignment, exportAssignmentIsAlias, ExportDeclaration, ExportSpecifier, Expression, expressionResultIsUnused, ExpressionStatement, ExpressionWithTypeArguments, Extension, ExternalEmitHelpers, externalHelpersModuleNameText, factory, fileExtensionIs, fileExtensionIsOneOf, filter, find, findAncestor, findBestPatternMatch, findConstructorDeclaration, findIndex, findLast, findLastIndex, findUseStrictPrologue, first, firstDefined, firstIterator, firstOrUndefined, firstOrUndefinedIterator, flatMap, flatten, FlowArrayMutation, FlowAssignment, FlowCall, FlowCondition, FlowFlags, FlowLabel, FlowNode, FlowReduceLabel, FlowStart, FlowSwitchClause, FlowSwitchClauseData, FlowType, forEach, forEachChild, forEachChildRecursively, forEachEnclosingBlockScopeContainer, forEachEntry, forEachKey, forEachReturnStatement, forEachYieldExpression, ForInOrOfStatement, ForInStatement, formatMessage, ForOfStatement, ForStatement, FreshableIntrinsicType, FreshableType, FreshObjectLiteralType, FunctionDeclaration, FunctionExpression, FunctionFlags, FunctionLikeDeclaration, FunctionOrConstructorTypeNode, FunctionTypeNode, GenericType, GetAccessorDeclaration, getAliasDeclarationFromName, getAllJSDocTags, getAllowSyntheticDefaultImports, getAncestor, getAnyExtensionFromPath, getAssignedExpandoInitializer, getAssignmentDeclarationKind, getAssignmentDeclarationPropertyAccessKind, getAssignmentTargetKind, getCanonicalDiagnostic, getCheckFlags, getClassExtendsHeritageElement, getClassLikeDeclarationOfSymbol, getCombinedLocalAndExportSymbolFlags, getCombinedModifierFlags, getCombinedNodeFlags, getCommonSourceDirectoryOfConfig, getContainingClass, getContainingClassExcludingClassDecorators, getContainingClassStaticBlock, getContainingFunction, getContainingFunctionOrClassStaticBlock, getDeclarationFileExtension, getDeclarationModifierFlagsFromSymbol, getDeclarationOfKind, getDeclarationsOfKind, getDeclaredExpandoInitializer, getDecorators, getDirectoryPath, getEffectiveBaseTypeNode, getEffectiveConstraintOfTypeParameter, getEffectiveContainerForJSDocTemplateTag, getEffectiveImplementsTypeNodes, getEffectiveInitializer, getEffectiveJSDocHost, getEffectiveModifierFlags, getEffectiveReturnTypeNode, getEffectiveSetAccessorTypeAnnotationNode, getEffectiveTypeAnnotationNode, getEffectiveTypeParameterDeclarations, getElementOrPropertyAccessName, getEmitDeclarations, getEmitModuleKind, getEmitModuleResolutionKind, getEmitScriptTarget, getEmitStandardClassFields, getEnclosingBlockScopeContainer, getEnclosingContainer, getEntityNameFromTypeNode, getErrorSpanForNode, getEscapedTextOfIdentifierOrLiteral, getEscapedTextOfJsxAttributeName, getEscapedTextOfJsxNamespacedName, getESModuleInterop, getExpandoInitializer, getExportAssignmentExpression, getExternalModuleImportEqualsDeclarationExpression, getExternalModuleName, getExternalModuleRequireArgument, getFirstConstructorWithBody, getFirstIdentifier, getFunctionFlags, getHostSignatureFromJSDoc, getIdentifierGeneratedImportReference, getIdentifierTypeArguments, getImmediatelyInvokedFunctionExpression, getInitializerOfBinaryExpression, getInterfaceBaseTypeNodes, getInvokedExpression, getIsolatedModules, getJSDocClassTag, getJSDocDeprecatedTag, getJSDocEnumTag, getJSDocHost, getJSDocOverloadTags, getJSDocParameterTags, getJSDocRoot, getJSDocSatisfiesExpressionType, getJSDocTags, getJSDocThisTag, getJSDocType, getJSDocTypeAssertionType, getJSDocTypeParameterDeclarations, getJSDocTypeTag, getJSXImplicitImportBase, getJSXRuntimeImport, getJSXTransformEnabled, getLeftmostAccessExpression, getLineAndCharacterOfPosition, getMembersOfDeclaration, getModifiers, getModuleInstanceState, getNameFromImportAttribute, getNameFromIndexInfo, getNameOfDeclaration, getNameOfExpando, getNamespaceDeclarationNode, getNewTargetContainer, getNonAugmentationDeclaration, getNonModifierTokenPosOfNode, getNormalizedAbsolutePath, getObjectFlags, getOriginalNode, getOrUpdate, getParameterSymbolFromJSDoc, getParseTreeNode, getPropertyAssignmentAliasLikeExpression, getPropertyNameForPropertyNameNode, getPropertyNameFromType, getRelativePathFromDirectory, getRelativePathFromFile, getResolutionDiagnostic, getResolutionModeOverride, getResolveJsonModule, getRestParameterElementType, getRootDeclaration, getScriptTargetFeatures, getSelectedEffectiveModifierFlags, getSemanticJsxChildren, getSetAccessorValueParameter, getSingleVariableOfVariableStatement, getSourceFileOfModule, getSourceFileOfNode, getSpanOfTokenAtPosition, getSpellingSuggestion, getStrictOptionValue, getSuperContainer, getSymbolNameForPrivateIdentifier, getSynthesizedDeepClone, getTextOfIdentifierOrLiteral, getTextOfJSDocComment, getTextOfJsxAttributeName, getTextOfNode, getTextOfPropertyName, getThisContainer, getThisParameter, getTrailingSemicolonDeferringWriter, getTypeParameterFromJsDoc, getUseDefineForClassFields, group, hasAbstractModifier, hasAccessorModifier, hasAmbientModifier, hasContextSensitiveParameters, HasDecorators, hasDecorators, hasDynamicName, hasEffectiveModifier, hasEffectiveModifiers, hasEffectiveReadonlyModifier, HasExpressionInitializer, hasExtension, HasIllegalDecorators, HasIllegalModifiers, HasInferredType, hasInferredType, HasInitializer, hasInitializer, hasJSDocNodes, hasJSDocParameterTags, hasJsonModuleEmitEnabled, HasLocals, HasModifiers, hasOnlyExpressionInitializer, hasOverrideModifier, hasPossibleExternalModuleReference, hasQuestionToken, hasResolutionModeOverride, hasRestParameter, hasScopeMarker, hasStaticModifier, hasSyntacticModifier, hasSyntacticModifiers, hasType, HeritageClause, hostGetCanonicalFileName, Identifier, identifierToKeywordKind, IdentifierTypePredicate, idText, IfStatement, ImportAttribute, ImportAttributes, ImportCall, ImportClause, ImportDeclaration, ImportEqualsDeclaration, ImportOrExportSpecifier, ImportSpecifier, ImportTypeNode, IndexedAccessType, IndexedAccessTypeNode, IndexFlags, IndexInfo, IndexKind, indexOfNode, IndexSignatureDeclaration, IndexType, indicesOf, InferenceContext, InferenceFlags, InferenceInfo, InferencePriority, InferTypeNode, InstanceofExpression, InstantiableType, InstantiationExpressionType, InterfaceDeclaration, InterfaceType, InterfaceTypeWithDeclaredMembers, InternalNodeBuilderFlags, InternalSymbolName, IntersectionFlags, IntersectionType, IntersectionTypeNode, intrinsicTagNameToString, IntrinsicType, introducesArgumentsExoticObject, IntroducesNewScopeNode, isAccessExpression, isAccessor, isAccessorModifier, isAliasableExpression, isAmbientModule, isArray, isArrayBindingPattern, isArrayLiteralExpression, isArrowFunction, isAssertionExpression, isAssignmentDeclaration, isAssignmentExpression, isAssignmentOperator, isAssignmentPattern, isAssignmentTarget, isAutoAccessorPropertyDeclaration, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBinaryLogicalOperator, isBindableObjectDefinePropertyCall, isBindableStaticElementAccessExpression, isBindableStaticNameExpression, isBindingElement, isBindingElementOfBareOrAccessedRequire, isBindingPattern, isBlock, isBlockOrCatchScoped, isBlockScopedContainerTopLevel, isBooleanLiteral, isCallChain, isCallExpression, isCallLikeExpression, isCallLikeOrFunctionLikeExpression, isCallOrNewExpression, isCallSignatureDeclaration, isCatchClause, isCatchClauseVariableDeclaration, isCatchClauseVariableDeclarationOrBindingElement, isCheckJsEnabledForFile, isClassDeclaration, isClassElement, isClassExpression, isClassInstanceProperty, isClassLike, isClassStaticBlockDeclaration, isCommaSequence, isCommonJsExportedExpression, isCommonJsExportPropertyAssignment, isCompoundAssignment, isComputedNonLiteralName, isComputedPropertyName, isConditionalExpression, isConditionalTypeNode, isConstAssertion, isConstructorDeclaration, isConstructorTypeNode, isConstructSignatureDeclaration, isConstTypeReference, isDeclaration, isDeclarationFileName, isDeclarationName, isDeclarationReadonly, isDecorator, isDefaultedExpandoInitializer, isDeleteTarget, isDottedName, isDynamicName, isEffectiveExternalModule, isElementAccessExpression, isEntityName, isEntityNameExpression, isEnumConst, isEnumDeclaration, isEnumMember, isExclusivelyTypeOnlyImportOrExport, isExpandoPropertyDeclaration, isExportAssignment, isExportDeclaration, isExportsIdentifier, isExportSpecifier, isExpression, isExpressionNode, isExpressionOfOptionalChainRoot, isExpressionStatement, isExpressionWithTypeArguments, isExpressionWithTypeArgumentsInClassExtendsClause, isExternalModule, isExternalModuleAugmentation, isExternalModuleImportEqualsDeclaration, isExternalModuleIndicator, isExternalModuleNameRelative, isExternalModuleReference, isExternalModuleSymbol, isExternalOrCommonJsModule, isForInOrOfStatement, isForInStatement, isForOfStatement, isForStatement, isFunctionDeclaration, isFunctionExpression, isFunctionExpressionOrArrowFunction, isFunctionLike, isFunctionLikeDeclaration, isFunctionLikeOrClassStaticBlockDeclaration, isFunctionOrModuleBlock, isFunctionTypeNode, isGeneratedIdentifier, isGetAccessor, isGetAccessorDeclaration, isGetOrSetAccessorDeclaration, isGlobalScopeAugmentation, isGlobalSourceFile, isHeritageClause, isIdentifier, isIdentifierText, isIdentifierTypePredicate, isIdentifierTypeReference, isIfStatement, isImportAttributes, isImportCall, isImportClause, isImportDeclaration, isImportEqualsDeclaration, isImportKeyword, isImportOrExportSpecifier, isImportSpecifier, isImportTypeNode, isInCompoundLikeAssignment, isIndexedAccessTypeNode, isIndexSignatureDeclaration, isInExpressionContext, isInfinityOrNaNString, isInitializedProperty, isInJSDoc, isInJSFile, isInJsonFile, isInstanceOfExpression, isInterfaceDeclaration, isInternalModuleImportEqualsDeclaration, isInTopLevelContext, isIntrinsicJsxName, isInTypeQuery, isIterationStatement, isJSDocAugmentsTag, isJSDocCallbackTag, isJSDocConstructSignature, isJSDocFunctionType, isJSDocImportTag, isJSDocIndexSignature, isJSDocLinkLike, isJSDocMemberName, isJSDocNameReference, isJSDocNode, isJSDocNonNullableType, isJSDocNullableType, isJSDocOptionalParameter, isJSDocOverloadTag, isJSDocParameterTag, isJSDocPropertyLikeTag, isJSDocPropertyTag, isJSDocSatisfiesExpression, isJSDocSatisfiesTag, isJSDocSignature, isJSDocTemplateTag, isJSDocThisTag, isJSDocTypeAlias, isJSDocTypeAssertion, isJSDocTypedefTag, isJSDocTypeExpression, isJSDocTypeLiteral, isJSDocVariadicType, isJsonSourceFile, isJsxAttribute, isJsxAttributeLike, isJsxAttributes, isJsxCallLike, isJsxElement, isJsxFragment, isJsxNamespacedName, isJsxOpeningElement, isJsxOpeningFragment, isJsxOpeningLikeElement, isJsxSelfClosingElement, isJsxSpreadAttribute, isJSXTagName, isKnownSymbol, isLateVisibilityPaintedStatement, isLeftHandSideExpression, isLineBreak, isLiteralComputedPropertyDeclarationName, isLiteralExpression, isLiteralExpressionOfObject, isLiteralImportTypeNode, isLiteralTypeNode, isLogicalOrCoalescingBinaryExpression, isLogicalOrCoalescingBinaryOperator, isMetaProperty, isMethodDeclaration, isMethodSignature, isModifier, isModuleBlock, isModuleDeclaration, isModuleExportsAccessExpression, isModuleIdentifier, isModuleOrEnumDeclaration, isModuleWithStringLiteralName, isNamedDeclaration, isNamedEvaluationSource, isNamedExports, isNamedTupleMember, isNamespaceExport, isNamespaceExportDeclaration, isNamespaceReexportDeclaration, isNewExpression, isNodeDescendantOf, isNonNullAccess, isNonNullExpression, isNumericLiteral, isNumericLiteralName, isObjectBindingPattern, isObjectLiteralElementLike, isObjectLiteralExpression, isObjectLiteralMethod, isObjectLiteralOrClassExpressionMethodOrAccessor, isOmittedExpression, isOptionalChain, isOptionalChainRoot, isOptionalDeclaration, isOptionalJSDocPropertyLikeTag, isOptionalTypeNode, isOutermostOptionalChain, isParameter, isParameterPropertyDeclaration, isParenthesizedExpression, isParenthesizedTypeNode, isPartOfParameterDeclaration, isPartOfTypeNode, isPartOfTypeOnlyImportOrExportDeclaration, isPartOfTypeQuery, isPlainJsFile, isPrefixUnaryExpression, isPrivateIdentifier, isPrivateIdentifierClassElementDeclaration, isPrivateIdentifierPropertyAccessExpression, isPrivateIdentifierSymbol, isPropertyAccessEntityNameExpression, isPropertyAccessExpression, isPropertyAccessOrQualifiedName, isPropertyAccessOrQualifiedNameOrImportTypeNode, isPropertyAssignment, isPropertyDeclaration, isPropertyName, isPropertyNameLiteral, isPropertySignature, isPrototypeAccess, isPrototypePropertyAssignment, isPushOrUnshiftIdentifier, isQualifiedName, isRequireCall, isRestParameter, isRestTypeNode, isRightSideOfAccessExpression, isRightSideOfInstanceofExpression, isRightSideOfQualifiedNameOrPropertyAccess, isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName, isSameEntityName, isSetAccessor, isSetAccessorDeclaration, isShorthandAmbientModuleSymbol, isShorthandPropertyAssignment, isSideEffectImport, isSingleOrDoubleQuote, isSourceFile, isSourceFileJS, isSpreadAssignment, isSpreadElement, isStatement, isStatementWithLocals, isStatic, isString, isStringANonContextualKeyword, isStringLiteral, isStringLiteralLike, isStringOrNumericLiteralLike, isSuperCall, isSuperProperty, isTaggedTemplateExpression, isTemplateSpan, isThisContainerOrFunctionBlock, isThisIdentifier, isThisInitializedDeclaration, isThisInitializedObjectBindingExpression, isThisInTypeQuery, isThisProperty, isThisTypeParameter, isThisTypePredicate, isTransientSymbol, isTupleTypeNode, isTypeAlias, isTypeAliasDeclaration, isTypeDeclaration, isTypeLiteralNode, isTypeNode, isTypeNodeKind, isTypeOfExpression, isTypeOnlyImportDeclaration, isTypeOnlyImportOrExportDeclaration, isTypeOperatorNode, isTypeParameterDeclaration, isTypePredicateNode, isTypeQueryNode, isTypeReferenceNode, isTypeReferenceType, isTypeUsableAsPropertyName, isUMDExportSymbol, isValidBigIntString, isValidESSymbolDeclaration, isValidTypeOnlyAliasUseSite, isValueSignatureDeclaration, isVariableDeclaration, isVariableDeclarationInitializedToBareOrAccessedRequire, isVariableDeclarationInVariableStatement, isVariableDeclarationList, isVariableLike, isVariableStatement, isWriteAccess, isWriteOnlyAccess, IterableOrIteratorType, IterationTypes, JSDoc, JSDocAugmentsTag, JSDocCallbackTag, JSDocComment, JSDocFunctionType, JSDocImplementsTag, JSDocImportTag, JSDocLink, JSDocLinkCode, JSDocLinkPlain, JSDocMemberName, JSDocNullableType, JSDocOptionalType, JSDocOverloadTag, JSDocParameterTag, JSDocPrivateTag, JSDocPropertyLikeTag, JSDocPropertyTag, JSDocProtectedTag, JSDocPublicTag, JSDocSatisfiesTag, JSDocSignature, JSDocTemplateTag, JSDocThisTag, JSDocTypeAssertion, JSDocTypedefTag, JSDocTypeExpression, JSDocTypeLiteral, JSDocTypeReferencingNode, JSDocTypeTag, JSDocVariadicType, JsxAttribute, JsxAttributeLike, JsxAttributeName, JsxAttributes, JsxCallLike, JsxChild, JsxClosingElement, JsxElement, JsxEmit, JsxExpression, JsxFlags, JsxFragment, JsxNamespacedName, JsxOpeningElement, JsxOpeningFragment, JsxOpeningLikeElement, JsxReferenceKind, JsxSelfClosingElement, JsxSpreadAttribute, JsxTagNameExpression, KeywordTypeNode, LabeledStatement, LanguageFeatureMinimumTarget, last, lastOrUndefined, LateBoundBinaryExpressionDeclaration, LateBoundDeclaration, LateBoundName, LateVisibilityPaintedStatement, LazyNodeCheckFlags, length, LiteralExpression, LiteralType, LiteralTypeNode, map, mapDefined, MappedSymbol, MappedType, MappedTypeNode, MatchingKeys, maybeBind, MemberOverrideStatus, MetaProperty, MethodDeclaration, MethodSignature, minAndMax, MinusToken, Modifier, ModifierFlags, modifiersToFlags, modifierToFlag, ModuleBlock, ModuleDeclaration, ModuleExportName, moduleExportNameIsDefault, moduleExportNameTextEscaped, moduleExportNameTextUnescaped, ModuleInstanceState, ModuleKind, ModuleName, ModuleResolutionKind, ModuleSpecifierResolutionHost, moduleSupportsImportAttributes, Mutable, MutableNodeArray, NamedDeclaration, NamedExports, NamedImportsOrExports, NamedTupleMember, NamespaceDeclaration, NamespaceExport, NamespaceExportDeclaration, NamespaceImport, needsScopeMarker, NewExpression, Node, NodeArray, NodeBuilderFlags, nodeCanBeDecorated, NodeCheckFlags, NodeFlags, nodeHasName, nodeIsMissing, nodeIsPresent, nodeIsSynthesized, NodeLinks, nodeStartsNewLexicalEnvironment, NodeWithTypeArguments, NonNullChain, NonNullExpression, NoSubstitutionTemplateLiteral, not, noTruncationMaximumTruncationLength, NumberLiteralType, NumericLiteral, objectAllocator, ObjectBindingPattern, ObjectFlags, ObjectFlagsType, ObjectLiteralElementLike, ObjectLiteralExpression, ObjectType, OptionalChain, OptionalTypeNode, or, orderedRemoveItemAt, OuterExpressionKinds, ParameterDeclaration, parameterIsThisKeyword, ParameterPropertyDeclaration, ParenthesizedExpression, ParenthesizedTypeNode, parseIsolatedEntityName, parseNodeFactory, parsePseudoBigInt, parseValidBigInt, Path, pathIsRelative, PatternAmbientModule, PlusToken, PostfixUnaryExpression, PredicateSemantics, PrefixUnaryExpression, PrivateIdentifier, Program, PromiseOrAwaitableType, PropertyAccessChain, PropertyAccessEntityNameExpression, PropertyAccessExpression, PropertyAssignment, PropertyDeclaration, PropertyName, PropertySignature, PseudoBigInt, pseudoBigIntToString, PunctuationSyntaxKind, pushIfUnique, QualifiedName, QuestionToken, rangeEquals, rangeOfNode, rangeOfTypeParameters, ReadonlyKeyword, reduceLeft, RegularExpressionLiteral, RelationComparisonResult, relativeComplement, removeExtension, removePrefix, replaceElement, resolutionExtensionIsTSOrJson, ResolutionMode, ResolvedModuleFull, ResolvedType, resolvingEmptyArray, RestTypeNode, ReturnStatement, ReverseMappedSymbol, ReverseMappedType, sameMap, SatisfiesExpression, Scanner, scanTokenAtPosition, ScriptKind, ScriptTarget, SetAccessorDeclaration, setCommentRange as setCommentRangeWorker, setEmitFlags, setIdentifierTypeArguments, setNodeFlags, setOriginalNode, setParent, setSyntheticLeadingComments, setTextRange as setTextRangeWorker, setTextRangePosEnd, setValueDeclaration, ShorthandPropertyAssignment, shouldAllowImportingTsExtension, shouldPreserveConstEnums, shouldRewriteModuleSpecifier, Signature, SignatureDeclaration, SignatureFlags, SignatureKind, singleElementArray, skipOuterExpressions, skipParentheses, skipTrivia, skipTypeChecking, skipTypeParentheses, some, SourceFile, sourceFileMayBeEmitted, SpreadAssignment, SpreadElement, startsWith, Statement, StringLiteral, StringLiteralLike, StringLiteralType, StringMappingType, stripQuotes, StructuredType, SubstitutionType, SuperCall, SwitchStatement, Symbol, SymbolAccessibility, SymbolAccessibilityResult, SymbolFlags, SymbolFormatFlags, SymbolId, SymbolLinks, symbolName, SymbolTable, SymbolTracker, SymbolVisibilityResult, SyntacticTypeNodeBuilderContext, SyntacticTypeNodeBuilderResolver, SyntaxKind, SyntheticDefaultModuleType, SyntheticExpression, TaggedTemplateExpression, TemplateExpression, TemplateLiteralType, TemplateLiteralTypeNode, Ternary, textRangeContainsPositionInclusive, TextSpan, textSpanContainsPosition, textSpanEnd, ThisExpression, ThisTypeNode, ThrowStatement, TokenFlags, tokenToString, tracing, TracingNode, TrackedSymbol, TransientSymbol, TransientSymbolLinks, tryAddToSet, tryCast, tryExtractTSExtension, tryGetClassImplementingOrExtendingExpressionWithTypeArguments, tryGetExtensionFromPath, tryGetJSDocSatisfiesTypeNode, tryGetModuleSpecifierFromDeclaration, tryGetPropertyAccessOrIdentifierToString, TryStatement, TupleType, TupleTypeNode, TupleTypeReference, Type, TypeAliasDeclaration, TypeAssertion, TypeChecker, TypeCheckerHost, TypeComparer, TypeElement, TypeFlags, TypeFormatFlags, TypeId, TypeLiteralNode, TypeMapKind, TypeMapper, TypeNode, TypeNodeSyntaxKind, TypeOfExpression, TypeOnlyAliasDeclaration, TypeOnlyCompatibleAliasDeclaration, TypeOperatorNode, TypeParameter, TypeParameterDeclaration, TypePredicate, TypePredicateKind, TypePredicateNode, TypeQueryNode, TypeReference, TypeReferenceNode, TypeReferenceSerializationKind, TypeReferenceType, TypeVariable, unescapeLeadingUnderscores, UnionOrIntersectionType, UnionOrIntersectionTypeNode, UnionReduction, UnionType, UnionTypeNode, UniqueESSymbolType, usingSingleLineStringWriter, VariableDeclaration, VariableDeclarationList, VariableLikeDeclaration, VariableStatement, VarianceFlags, visitEachChild as visitEachChildWorker, visitNode, visitNodes, Visitor, VisitResult, VoidExpression, walkUpBindingElementsAndPatterns, walkUpOuterExpressions, walkUpParenthesizedExpressions, walkUpParenthesizedTypes, walkUpParenthesizedTypesAndGetParentAndChild, WhileStatement, WideningContext, WithStatement, WriterContextOut, YieldExpression, } from "./_namespaces/ts.js"; import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers.js"; import * as performance from "./_namespaces/ts.performance.js"; const ambientModuleSymbolRegex = /^".+"$/; const anon = "(anonymous)" as __String & string; const enum ReferenceHint { Unspecified, Identifier, Property, ExportAssignment, Jsx, AsyncFunction, ExportImportEquals, ExportSpecifier, Decorator, } let nextSymbolId = 1; let nextNodeId = 1; let nextMergeId = 1; let nextFlowId = 1; const enum IterationUse { AllowsSyncIterablesFlag = 1 GenericType; getGlobalIterableType: (reportErrors: boolean) => GenericType; getGlobalIterableIteratorType: (reportErrors: boolean) => GenericType; getGlobalIteratorObjectType: (reportErrors: boolean) => GenericType; getGlobalGeneratorType: (reportErrors: boolean) => GenericType; getGlobalBuiltinIteratorTypes: () => readonly GenericType[]; resolveIterationType: (type: Type, errorNode: Node | undefined) => Type | undefined; mustHaveANextMethodDiagnostic: DiagnosticMessage; mustBeAMethodDiagnostic: DiagnosticMessage; mustHaveAValueDiagnostic: DiagnosticMessage; } const enum WideningKind { Normal, FunctionReturn, GeneratorNext, GeneratorYield, } // dprint-ignore /** @internal */ export const enum TypeFacts { None = 0, TypeofEQString = 1 = new Map(Object.entries({ string: TypeFacts.TypeofNEString, number: TypeFacts.TypeofNENumber, bigint: TypeFacts.TypeofNEBigInt, boolean: TypeFacts.TypeofNEBoolean, symbol: TypeFacts.TypeofNESymbol, undefined: TypeFacts.NEUndefined, object: TypeFacts.TypeofNEObject, function: TypeFacts.TypeofNEFunction, })); type TypeSystemEntity = Node | Symbol | Type | Signature; const enum TypeSystemPropertyName { Type, ResolvedBaseConstructorType, DeclaredType, ResolvedReturnType, ImmediateBaseConstraint, ResolvedTypeArguments, ResolvedBaseTypes, WriteType, ParameterInitializerContainsUndefined, } // dprint-ignore /** @internal */ export const enum CheckMode { Normal = 0, // Normal type checking Contextual = 1 void; const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); const enum DeclarationMeaning { GetAccessor = 1, SetAccessor = 2, PropertyAssignment = 4, Method = 8, PrivateStatic = 16, GetOrSetAccessor = GetAccessor | SetAccessor, PropertyAssignmentOrMethod = PropertyAssignment | Method, } const enum DeclarationSpaces { None = 0, ExportValue = 1 = new Map(Object.entries({ Uppercase: IntrinsicTypeKind.Uppercase, Lowercase: IntrinsicTypeKind.Lowercase, Capitalize: IntrinsicTypeKind.Capitalize, Uncapitalize: IntrinsicTypeKind.Uncapitalize, NoInfer: IntrinsicTypeKind.NoInfer, })); const SymbolLinks = class implements SymbolLinks { declare _symbolLinksBrand: any; }; function NodeLinks(this: NodeLinks) { this.flags = NodeCheckFlags.None; } /** @internal */ export function getNodeId(node: Node): number { if (!node.id) { node.id = nextNodeId; nextNodeId++; } return node.id; } /** @internal */ export function getSymbolId(symbol: Symbol): SymbolId { if (!symbol.id) { symbol.id = nextSymbolId; nextSymbolId++; } return symbol.id; } /** @internal */ export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean): boolean { const moduleState = getModuleInstanceState(node); return moduleState === ModuleInstanceState.Instantiated || (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); } /** @internal */ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Why var? It avoids TDZ checks in the runtime which can be costly. // See: https://github.com/microsoft/TypeScript/issues/52924 /* eslint-disable no-var */ var deferredDiagnosticsCallbacks: (() => void)[] = []; var addLazyDiagnostic = (arg: () => void) => { deferredDiagnosticsCallbacks.push(arg); }; // Cancellation that controls whether or not we can cancel in the middle of type checking. // In general cancelling is *not* safe for the type checker. We might be in the middle of // computing something, and we will leave our internals in an inconsistent state. Callers // who set the cancellation token should catch if a cancellation exception occurs, and // should throw away and create a new TypeChecker. // // Currently we only support setting the cancellation token when getting diagnostics. This // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if // they no longer need the information (for example, if the user started editing again). var cancellationToken: CancellationToken | undefined; var scanner: Scanner | undefined; var Symbol = objectAllocator.getSymbolConstructor(); var Type = objectAllocator.getTypeConstructor(); var Signature = objectAllocator.getSignatureConstructor(); var typeCount = 0; var symbolCount = 0; var totalInstantiationCount = 0; var instantiationCount = 0; var instantiationDepth = 0; var inlineLevel = 0; var currentNode: Node | undefined; var varianceTypeParameter: TypeParameter | undefined; var isInferencePartiallyBlocked = false; var emptySymbols = createSymbolTable(); var arrayVariances = [VarianceFlags.Covariant]; var compilerOptions = host.getCompilerOptions(); var languageVersion = getEmitScriptTarget(compilerOptions); var moduleKind = getEmitModuleKind(compilerOptions); var legacyDecorators = !!compilerOptions.experimentalDecorators; var useDefineForClassFields = getUseDefineForClassFields(compilerOptions); var emitStandardClassFields = getEmitStandardClassFields(compilerOptions); var allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); var strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); var strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); var strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); var strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); var strictBuiltinIteratorReturn = getStrictOptionValue(compilerOptions, "strictBuiltinIteratorReturn"); var noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); var noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); var useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); var exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; var noUncheckedSideEffectImports = !!compilerOptions.noUncheckedSideEffectImports; var checkBinaryExpression = createCheckBinaryExpression(); var emitResolver = createResolver(); var nodeBuilder = createNodeBuilder(); var syntacticNodeBuilder = createSyntacticTypeNodeBuilder(compilerOptions, nodeBuilder.syntacticBuilderResolver); var evaluate = createEvaluator({ evaluateElementAccessExpression, evaluateEntityNameExpression, }); var globals = createSymbolTable(); var undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); undefinedSymbol.declarations = []; var globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); globalThisSymbol.exports = globals; globalThisSymbol.declarations = []; globals.set(globalThisSymbol.escapedName, globalThisSymbol); var argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); var requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); var isolatedModulesLikeFlagName = compilerOptions.verbatimModuleSyntax ? "verbatimModuleSyntax" : "isolatedModules"; var canCollectSymbolAliasAccessabilityData = !compilerOptions.verbatimModuleSyntax; /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ var apparentArgumentCount: number | undefined; var lastGetCombinedNodeFlagsNode: Node | undefined; var lastGetCombinedNodeFlagsResult = NodeFlags.None; var lastGetCombinedModifierFlagsNode: Declaration | undefined; var lastGetCombinedModifierFlagsResult = ModifierFlags.None; var resolveName = createNameResolver({ compilerOptions, requireSymbol, argumentsSymbol, globals, getSymbolOfDeclaration, error, getRequiresScopeChangeCache, setRequiresScopeChangeCache, lookup: getSymbol, onPropertyWithInvalidInitializer: checkAndReportErrorForInvalidInitializer, onFailedToResolveSymbol, onSuccessfullyResolvedSymbol, }); var resolveNameForSymbolSuggestion = createNameResolver({ compilerOptions, requireSymbol, argumentsSymbol, globals, getSymbolOfDeclaration, error, getRequiresScopeChangeCache, setRequiresScopeChangeCache, lookup: getSuggestionForSymbolNameLookup, }); // for public members that accept a Node or one of its subtypes, we must guard against // synthetic nodes created during transformations by calling `getParseTreeNode`. // for most of these, we perform the guard only on `checker` to avoid any possible // extra cost of calling `getParseTreeNode` when calling these functions from inside the // checker. const checker: TypeChecker = { getNodeCount: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.nodeCount, 0), getIdentifierCount: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.identifierCount, 0), getSymbolCount: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.symbolCount, symbolCount), getTypeCount: () => typeCount, getInstantiationCount: () => totalInstantiationCount, getRelationCacheSizes: () => ({ assignable: assignableRelation.size, identity: identityRelation.size, subtype: subtypeRelation.size, strictSubtype: strictSubtypeRelation.size, }), isUndefinedSymbol: symbol => symbol === undefinedSymbol, isArgumentsSymbol: symbol => symbol === argumentsSymbol, isUnknownSymbol: symbol => symbol === unknownSymbol, getMergedSymbol, symbolIsValue, getDiagnostics, getGlobalDiagnostics, getRecursionIdentity, getUnmatchedProperties, getTypeOfSymbolAtLocation: (symbol, locationIn) => { const location = getParseTreeNode(locationIn); return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType; }, getTypeOfSymbol, getSymbolsOfParameterPropertyDeclaration: (parameterIn, parameterName) => { const parameter = getParseTreeNode(parameterIn, isParameter); if (parameter === undefined) return Debug.fail("Cannot get symbols of a synthetic parameter that cannot be resolved to a parse-tree node."); Debug.assert(isParameterPropertyDeclaration(parameter, parameter.parent)); return getSymbolsOfParameterPropertyDeclaration(parameter, escapeLeadingUnderscores(parameterName)); }, getDeclaredTypeOfSymbol, getPropertiesOfType, getPropertyOfType: (type, name) => getPropertyOfType(type, escapeLeadingUnderscores(name)), getPrivateIdentifierPropertyOfType: (leftType: Type, name: string, location: Node) => { const node = getParseTreeNode(location); if (!node) { return undefined; } const propName = escapeLeadingUnderscores(name); const lexicallyScopedIdentifier = lookupSymbolForPrivateIdentifierDeclaration(propName, node); return lexicallyScopedIdentifier ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedIdentifier) : undefined; }, getTypeOfPropertyOfType: (type, name) => getTypeOfPropertyOfType(type, escapeLeadingUnderscores(name)), getIndexInfoOfType: (type, kind) => getIndexInfoOfType(type, kind === IndexKind.String ? stringType : numberType), getIndexInfosOfType, getIndexInfosOfIndexSymbol, getSignaturesOfType, getIndexTypeOfType: (type, kind) => getIndexTypeOfType(type, kind === IndexKind.String ? stringType : numberType), getIndexType: type => getIndexType(type), getBaseTypes, getBaseTypeOfLiteralType, getWidenedType, getWidenedLiteralType, fillMissingTypeArguments, getTypeFromTypeNode: nodeIn => { const node = getParseTreeNode(nodeIn, isTypeNode); return node ? getTypeFromTypeNode(node) : errorType; }, getParameterType: getTypeAtPosition, getParameterIdentifierInfoAtPosition, getPromisedTypeOfPromise, getAwaitedType: type => getAwaitedType(type), getReturnTypeOfSignature, isNullableType, getNullableType, getNonNullableType, getNonOptionalType: removeOptionalTypeMarker, getTypeArguments, typeToTypeNode: nodeBuilder.typeToTypeNode, typePredicateToTypePredicateNode: nodeBuilder.typePredicateToTypePredicateNode, indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, symbolToEntityName: nodeBuilder.symbolToEntityName, symbolToExpression: nodeBuilder.symbolToExpression, symbolToNode: nodeBuilder.symbolToNode, symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations, symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration, typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration, getSymbolsInScope: (locationIn, meaning) => { const location = getParseTreeNode(locationIn); return location ? getSymbolsInScope(location, meaning) : []; }, getSymbolAtLocation: nodeIn => { const node = getParseTreeNode(nodeIn); // set ignoreErrors: true because any lookups invoked by the API shouldn't cause any new errors return node ? getSymbolAtLocation(node, /*ignoreErrors*/ true) : undefined; }, getIndexInfosAtLocation: nodeIn => { const node = getParseTreeNode(nodeIn); return node ? getIndexInfosAtLocation(node) : undefined; }, getShorthandAssignmentValueSymbol: nodeIn => { const node = getParseTreeNode(nodeIn); return node ? getShorthandAssignmentValueSymbol(node) : undefined; }, getExportSpecifierLocalTargetSymbol: nodeIn => { const node = getParseTreeNode(nodeIn, isExportSpecifier); return node ? getExportSpecifierLocalTargetSymbol(node) : undefined; }, getExportSymbolOfSymbol(symbol) { return getMergedSymbol(symbol.exportSymbol || symbol); }, getTypeAtLocation: nodeIn => { const node = getParseTreeNode(nodeIn); return node ? getTypeOfNode(node) : errorType; }, getTypeOfAssignmentPattern: nodeIn => { const node = getParseTreeNode(nodeIn, isAssignmentPattern); return node && getTypeOfAssignmentPattern(node) || errorType; }, getPropertySymbolOfDestructuringAssignment: locationIn => { const location = getParseTreeNode(locationIn, isIdentifier); return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined; }, signatureToString: (signature, enclosingDeclaration, flags, kind) => { return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind); }, typeToString: (type, enclosingDeclaration, flags) => { return typeToString(type, getParseTreeNode(enclosingDeclaration), flags); }, symbolToString: (symbol, enclosingDeclaration, meaning, flags) => { return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags); }, typePredicateToString: (predicate, enclosingDeclaration, flags) => { return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags); }, writeSignature: (signature, enclosingDeclaration, flags, kind, writer, maximumLength, verbosityLevel, out) => { return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer, maximumLength, verbosityLevel, out); }, writeType: (type, enclosingDeclaration, flags, writer, maximumLength, verbosityLevel, out) => { return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer, maximumLength, verbosityLevel, out); }, writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => { return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer); }, writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => { return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer); }, getAugmentedPropertiesOfType, getRootSymbols, getSymbolOfExpando, getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { const node = getParseTreeNode(nodeIn, isExpression); if (!node) { return undefined; } if (contextFlags! & ContextFlags.Completions) { return runWithInferenceBlockedFromSourceNode(node, () => getContextualType(node, contextFlags)); } return getContextualType(node, contextFlags); }, getContextualTypeForObjectLiteralElement: nodeIn => { const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); return node ? getContextualTypeForObjectLiteralElement(node, /*contextFlags*/ undefined) : undefined; }, getContextualTypeForArgumentAtIndex: (nodeIn, argIndex) => { const node = getParseTreeNode(nodeIn, isCallLikeExpression); return node && getContextualTypeForArgumentAtIndex(node, argIndex); }, getContextualTypeForJsxAttribute: nodeIn => { const node = getParseTreeNode(nodeIn, isJsxAttributeLike); return node && getContextualTypeForJsxAttribute(node, /*contextFlags*/ undefined); }, isContextSensitive, getTypeOfPropertyOfContextualType, getFullyQualifiedName, getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), getCandidateSignaturesForStringLiteralCompletions, getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp)), getExpandedParameters, hasEffectiveRestParameter, containsArgumentsReference, getConstantValue: nodeIn => { const node = getParseTreeNode(nodeIn, canHaveConstantValue); return node ? getConstantValue(node) : undefined; }, isValidPropertyAccess: (nodeIn, propertyName) => { const node = getParseTreeNode(nodeIn, isPropertyAccessOrQualifiedNameOrImportTypeNode); return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)); }, isValidPropertyAccessForCompletions: (nodeIn, type, property) => { const node = getParseTreeNode(nodeIn, isPropertyAccessExpression); return !!node && isValidPropertyAccessForCompletions(node, type, property); }, getSignatureFromDeclaration: declarationIn => { const declaration = getParseTreeNode(declarationIn, isFunctionLike); return declaration ? getSignatureFromDeclaration(declaration) : undefined; }, isImplementationOfOverload: nodeIn => { const node = getParseTreeNode(nodeIn, isFunctionLike); return node ? isImplementationOfOverload(node) : undefined; }, getImmediateAliasedSymbol, getAliasedSymbol: resolveAlias, getEmitResolver, requiresAddingImplicitUndefined, getExportsOfModule: getExportsOfModuleAsArray, getExportsAndPropertiesOfModule, forEachExportAndPropertyOfModule, getSymbolWalker: createGetSymbolWalker( getRestTypeOfSignature, getTypePredicateOfSignature, getReturnTypeOfSignature, getBaseTypes, resolveStructuredTypeMembers, getTypeOfSymbol, getResolvedSymbol, getConstraintOfTypeParameter, getFirstIdentifier, getTypeArguments, ), getAmbientModules, getJsxIntrinsicTagNamesAt, isOptionalParameter: nodeIn => { const node = getParseTreeNode(nodeIn, isParameter); return node ? isOptionalParameter(node) : false; }, tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol), tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol), tryFindAmbientModule: moduleName => tryFindAmbientModule(moduleName, /*withAugmentations*/ true), getApparentType, getUnionType, isTypeAssignableTo, createAnonymousType, createSignature, createSymbol, createIndexInfo, getAnyType: () => anyType, getStringType: () => stringType, getStringLiteralType, getNumberType: () => numberType, getNumberLiteralType, getBigIntType: () => bigintType, getBigIntLiteralType, getUnknownType: () => unknownType, createPromiseType, createArrayType, getElementTypeOfArrayType, getBooleanType: () => booleanType, getFalseType: (fresh?) => fresh ? falseType : regularFalseType, getTrueType: (fresh?) => fresh ? trueType : regularTrueType, getVoidType: () => voidType, getUndefinedType: () => undefinedType, getNullType: () => nullType, getESSymbolType: () => esSymbolType, getNeverType: () => neverType, getNonPrimitiveType: () => nonPrimitiveType, getOptionalType: () => optionalType, getPromiseType: () => getGlobalPromiseType(/*reportErrors*/ false), getPromiseLikeType: () => getGlobalPromiseLikeType(/*reportErrors*/ false), getAnyAsyncIterableType: () => { const type = getGlobalAsyncIterableType(/*reportErrors*/ false); if (type === emptyGenericType) return undefined; return createTypeReference(type, [anyType, anyType, anyType]); }, isSymbolAccessible, isArrayType, isTupleType, isArrayLikeType, isEmptyAnonymousObjectType, isTypeInvalidDueToUnionDiscriminant, getExactOptionalProperties, getAllPossiblePropertiesOfTypes, getSuggestedSymbolForNonexistentProperty, getSuggestedSymbolForNonexistentJSXAttribute, getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), getSuggestedSymbolForNonexistentModule, getSuggestedSymbolForNonexistentClassMember, getBaseConstraintOfType, getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined, resolveName(name, location, meaning, excludeGlobals) { return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false, excludeGlobals); }, getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)), getJsxFragmentFactory: n => { const jsxFragmentFactory = getJsxFragmentFactoryEntity(n); return jsxFragmentFactory && unescapeLeadingUnderscores(getFirstIdentifier(jsxFragmentFactory).escapedText); }, getAccessibleSymbolChain, getTypePredicateOfSignature, resolveExternalModuleName: moduleSpecifierIn => { const moduleSpecifier = getParseTreeNode(moduleSpecifierIn, isExpression); return moduleSpecifier && resolveExternalModuleName(moduleSpecifier, moduleSpecifier, /*ignoreErrors*/ true); }, resolveExternalModuleSymbol, tryGetThisTypeAt: (nodeIn, includeGlobalThis, container) => { const node = getParseTreeNode(nodeIn); return node && tryGetThisTypeAt(node, includeGlobalThis, container); }, getTypeArgumentConstraint: nodeIn => { const node = getParseTreeNode(nodeIn, isTypeNode); return node && getTypeArgumentConstraint(node); }, getSuggestionDiagnostics: (fileIn, ct) => { const file = getParseTreeNode(fileIn, isSourceFile) || Debug.fail("Could not determine parsed source file."); if (skipTypeChecking(file, compilerOptions, host)) { return emptyArray; } let diagnostics: DiagnosticWithLocation[] | undefined; try { // Record the cancellation token so it can be checked later on during checkSourceElement. // Do this in a finally block so we can ensure that it gets reset back to nothing after // this call is done. cancellationToken = ct; // Ensure file is type checked, with _eager_ diagnostic production, so identifiers are registered as potentially unused checkSourceFileWithEagerDiagnostics(file); Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked)); diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName)); checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (containingNode, kind, diag) => { if (!containsParseError(containingNode) && !unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { (diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion }); } }); return diagnostics || emptyArray; } finally { cancellationToken = undefined; } }, runWithCancellationToken: (token, callback) => { try { cancellationToken = token; return callback(checker); } finally { cancellationToken = undefined; } }, getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, isDeclarationVisible, isPropertyAccessible, getTypeOnlyAliasDeclaration, getMemberOverrideModifierStatus, isTypeParameterPossiblyReferenced, typeHasCallOrConstructSignatures, getSymbolFlags, getTypeArgumentsForResolvedSignature, isLibType, }; function getTypeArgumentsForResolvedSignature(signature: Signature) { if (signature.mapper === undefined) return undefined; return instantiateTypes((signature.target || signature).typeParameters, signature.mapper); } function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node) { const candidatesSet = new Set(); const candidates: Signature[] = []; // first, get candidates when inference is blocked from the source node. runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.Normal)); for (const candidate of candidates) { candidatesSet.add(candidate); } // reset candidates for second pass candidates.length = 0; // next, get candidates where the source node is considered for inference. runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.Normal)); for (const candidate of candidates) { candidatesSet.add(candidate); } return arrayFrom(candidatesSet); } function runWithoutResolvedSignatureCaching(node: Node | undefined, fn: () => T): T { node = findAncestor(node, isCallLikeOrFunctionLikeExpression); if (node) { const cachedResolvedSignatures = []; const cachedTypes = []; while (node) { const nodeLinks = getNodeLinks(node); cachedResolvedSignatures.push([nodeLinks, nodeLinks.resolvedSignature] as const); nodeLinks.resolvedSignature = undefined; if (isFunctionExpressionOrArrowFunction(node)) { const symbolLinks = getSymbolLinks(getSymbolOfDeclaration(node)); const type = symbolLinks.type; cachedTypes.push([symbolLinks, type] as const); symbolLinks.type = undefined; } node = findAncestor(node.parent, isCallLikeOrFunctionLikeExpression); } const result = fn(); for (const [nodeLinks, resolvedSignature] of cachedResolvedSignatures) { nodeLinks.resolvedSignature = resolvedSignature; } for (const [symbolLinks, type] of cachedTypes) { symbolLinks.type = type; } return result; } return fn(); } function runWithInferenceBlockedFromSourceNode(node: Node | undefined, fn: () => T): T { const containingCall = findAncestor(node, isCallLikeExpression); if (containingCall) { let toMarkSkip = node!; do { getNodeLinks(toMarkSkip).skipDirectInference = true; toMarkSkip = toMarkSkip.parent; } while (toMarkSkip && toMarkSkip !== containingCall); } isInferencePartiallyBlocked = true; const result = runWithoutResolvedSignatureCaching(node, fn); isInferencePartiallyBlocked = false; if (containingCall) { let toMarkSkip = node!; do { getNodeLinks(toMarkSkip).skipDirectInference = undefined; toMarkSkip = toMarkSkip.parent; } while (toMarkSkip && toMarkSkip !== containingCall); } return result; } function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { const node = getParseTreeNode(nodeIn, isCallLikeExpression); apparentArgumentCount = argumentCount; const res = !node ? undefined : getResolvedSignature(node, candidatesOutArray, checkMode); apparentArgumentCount = undefined; return res; } var tupleTypes = new Map(); var unionTypes = new Map(); var unionOfUnionTypes = new Map(); var intersectionTypes = new Map(); var stringLiteralTypes = new Map(); var numberLiteralTypes = new Map(); var bigIntLiteralTypes = new Map(); var enumLiteralTypes = new Map(); var indexedAccessTypes = new Map(); var templateLiteralTypes = new Map(); var stringMappingTypes = new Map(); var substitutionTypes = new Map(); var subtypeReductionCache = new Map(); var decoratorContextOverrideTypeCache = new Map(); var cachedTypes = new Map(); var evolvingArrayTypes: EvolvingArrayType[] = []; var undefinedProperties: SymbolTable = new Map(); var markerTypes = new Set(); var unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); var resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); var unresolvedSymbols = new Map(); var errorTypes = new Map(); // We specifically create the `undefined` and `null` types before any other types that can occur in // unions such that they are given low type IDs and occur first in the sorted list of union constituents. // We can then just examine the first constituent(s) of a union to check for their presence. var seenIntrinsicNames = new Set(); var anyType = createIntrinsicType(TypeFlags.Any, "any"); var autoType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.NonInferrableType, "auto"); var wildcardType = createIntrinsicType(TypeFlags.Any, "any", /*objectFlags*/ undefined, "wildcard"); var blockedStringType = createIntrinsicType(TypeFlags.Any, "any", /*objectFlags*/ undefined, "blocked string"); var errorType = createIntrinsicType(TypeFlags.Any, "error"); var unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); var nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType, "non-inferrable"); var intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); var unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); var undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); var undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType, "widening"); var missingType = createIntrinsicType(TypeFlags.Undefined, "undefined", /*objectFlags*/ undefined, "missing"); var undefinedOrMissingType = exactOptionalPropertyTypes ? missingType : undefinedType; var optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined", /*objectFlags*/ undefined, "optional"); var nullType = createIntrinsicType(TypeFlags.Null, "null"); var nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType, "widening"); var stringType = createIntrinsicType(TypeFlags.String, "string"); var numberType = createIntrinsicType(TypeFlags.Number, "number"); var bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); var falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false", /*objectFlags*/ undefined, "fresh") as FreshableIntrinsicType; var regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; var trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true", /*objectFlags*/ undefined, "fresh") as FreshableIntrinsicType; var regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; trueType.regularType = regularTrueType; trueType.freshType = trueType; regularTrueType.regularType = regularTrueType; regularTrueType.freshType = trueType; falseType.regularType = regularFalseType; falseType.freshType = falseType; regularFalseType.regularType = regularFalseType; regularFalseType.freshType = falseType; var booleanType = getUnionType([regularFalseType, regularTrueType]); var esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); var voidType = createIntrinsicType(TypeFlags.Void, "void"); var neverType = createIntrinsicType(TypeFlags.Never, "never"); var silentNeverType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType, "silent"); var implicitNeverType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "implicit"); var unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "unreachable"); var nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); var stringOrNumberType = getUnionType([stringType, numberType]); var stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); var numberOrBigIntType = getUnionType([numberType, bigintType]); var templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; var numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type var restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t, () => "(restrictive mapper)"); var permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t, () => "(permissive mapper)"); var uniqueLiteralType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "unique literal"); // `uniqueLiteralType` is a special `never` flagged by union reduction to behave as a literal var uniqueLiteralMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? uniqueLiteralType : t, () => "(unique literal mapper)"); // replace all type parameters with the unique literal type (disregarding constraints) var outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; var reportUnreliableMapper = makeFunctionTypeMapper(t => { if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); } return t; }, () => "(unmeasurable reporter)"); var reportUnmeasurableMapper = makeFunctionTypeMapper(t => { if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); } return t; }, () => "(unreliable reporter)"); var emptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); var emptyJsxObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; var emptyFreshJsxObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); emptyFreshJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes | ObjectFlags.FreshLiteral | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; var emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); emptyTypeLiteralSymbol.members = createSymbolTable(); var emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); var unknownEmptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); var unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType; var emptyGenericType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; emptyGenericType.instantiations = new Map(); var anyFunctionType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; var noConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); var circularConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); var resolvingDefaultType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); var markerSuperType = createTypeParameter(); var markerSubType = createTypeParameter(); markerSubType.constraint = markerSuperType; var markerOtherType = createTypeParameter(); var markerSuperTypeForCheck = createTypeParameter(); var markerSubTypeForCheck = createTypeParameter(); markerSubTypeForCheck.constraint = markerSuperTypeForCheck; var noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, ">", 0, anyType); var anySignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); var unknownSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); var resolvingSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); var silentNeverSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); var enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); var anyBaseTypeIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); var iterationTypesCache = new Map(); // cache for common IterationTypes instances var noIterationTypes: IterationTypes = { get yieldType(): Type { return Debug.fail("Not supported"); }, get returnType(): Type { return Debug.fail("Not supported"); }, get nextType(): Type { return Debug.fail("Not supported"); }, }; var anyIterationTypes = createIterationTypes(anyType, anyType, anyType); var silentNeverIterationTypes = createIterationTypes(silentNeverType, silentNeverType, silentNeverType); var asyncIterationTypesResolver: IterationTypesResolver = { iterableCacheKey: "iterationTypesOfAsyncIterable", iteratorCacheKey: "iterationTypesOfAsyncIterator", iteratorSymbolName: "asyncIterator", getGlobalIteratorType: getGlobalAsyncIteratorType, getGlobalIterableType: getGlobalAsyncIterableType, getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, getGlobalIteratorObjectType: getGlobalAsyncIteratorObjectType, getGlobalGeneratorType: getGlobalAsyncGeneratorType, getGlobalBuiltinIteratorTypes: getGlobalBuiltinAsyncIteratorTypes, resolveIterationType: (type, errorNode) => getAwaitedType(type, errorNode, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member), mustHaveANextMethodDiagnostic: Diagnostics.An_async_iterator_must_have_a_next_method, mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_async_iterator_must_be_a_method, mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property, }; var syncIterationTypesResolver: IterationTypesResolver = { iterableCacheKey: "iterationTypesOfIterable", iteratorCacheKey: "iterationTypesOfIterator", iteratorSymbolName: "iterator", getGlobalIteratorType, getGlobalIterableType, getGlobalIterableIteratorType, getGlobalIteratorObjectType, getGlobalGeneratorType, getGlobalBuiltinIteratorTypes, resolveIterationType: (type, _errorNode) => type, mustHaveANextMethodDiagnostic: Diagnostics.An_iterator_must_have_a_next_method, mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_iterator_must_be_a_method, mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_iterator_must_have_a_value_property, }; interface DuplicateInfoForSymbol { readonly firstFileLocations: Declaration[]; readonly secondFileLocations: Declaration[]; readonly isBlockScoped: boolean; } interface DuplicateInfoForFiles { readonly firstFile: SourceFile; readonly secondFile: SourceFile; /** Key is symbol name. */ readonly conflictingSymbols: Map; } /** Key is "/path/to/a.ts|/path/to/b.ts". */ var amalgamatedDuplicates: Map | undefined; var reverseMappedCache = new Map(); var reverseHomomorphicMappedCache = new Map(); var ambientModulesCache: Symbol[] | undefined; /** * List of every ambient module with a "*" wildcard. * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. * This is only used if there is no exact match. */ var patternAmbientModules: PatternAmbientModule[]; var patternAmbientModuleAugmentations: Map | undefined; var globalObjectType: ObjectType; var globalFunctionType: ObjectType; var globalCallableFunctionType: ObjectType; var globalNewableFunctionType: ObjectType; var globalArrayType: GenericType; var globalReadonlyArrayType: GenericType; var globalStringType: ObjectType; var globalNumberType: ObjectType; var globalBooleanType: ObjectType; var globalRegExpType: ObjectType; var globalThisType: GenericType; var anyArrayType: Type; var autoArrayType: Type; var anyReadonlyArrayType: Type; var deferredGlobalNonNullableTypeAlias: Symbol; // The library files are only loaded when the feature is used. // This allows users to just specify library files they want to used through --lib // and they will not get an error from not having unrelated library files var deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; var deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; var deferredGlobalESSymbolType: ObjectType | undefined; var deferredGlobalTypedPropertyDescriptorType: GenericType; var deferredGlobalPromiseType: GenericType | undefined; var deferredGlobalPromiseLikeType: GenericType | undefined; var deferredGlobalPromiseConstructorSymbol: Symbol | undefined; var deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; var deferredGlobalIterableType: GenericType | undefined; var deferredGlobalIteratorType: GenericType | undefined; var deferredGlobalIterableIteratorType: GenericType | undefined; var deferredGlobalIteratorObjectType: GenericType | undefined; var deferredGlobalGeneratorType: GenericType | undefined; var deferredGlobalIteratorYieldResultType: GenericType | undefined; var deferredGlobalIteratorReturnResultType: GenericType | undefined; var deferredGlobalAsyncIterableType: GenericType | undefined; var deferredGlobalAsyncIteratorType: GenericType | undefined; var deferredGlobalAsyncIterableIteratorType: GenericType | undefined; var deferredGlobalBuiltinIteratorTypes: readonly GenericType[] | undefined; var deferredGlobalBuiltinAsyncIteratorTypes: readonly GenericType[] | undefined; var deferredGlobalAsyncIteratorObjectType: GenericType | undefined; var deferredGlobalAsyncGeneratorType: GenericType | undefined; var deferredGlobalTemplateStringsArrayType: ObjectType | undefined; var deferredGlobalImportMetaType: ObjectType; var deferredGlobalImportMetaExpressionType: ObjectType; var deferredGlobalImportCallOptionsType: ObjectType | undefined; var deferredGlobalImportAttributesType: ObjectType | undefined; var deferredGlobalDisposableType: ObjectType | undefined; var deferredGlobalAsyncDisposableType: ObjectType | undefined; var deferredGlobalExtractSymbol: Symbol | undefined; var deferredGlobalOmitSymbol: Symbol | undefined; var deferredGlobalAwaitedSymbol: Symbol | undefined; var deferredGlobalBigIntType: ObjectType | undefined; var deferredGlobalNaNSymbol: Symbol | undefined; var deferredGlobalRecordSymbol: Symbol | undefined; var deferredGlobalClassDecoratorContextType: GenericType | undefined; var deferredGlobalClassMethodDecoratorContextType: GenericType | undefined; var deferredGlobalClassGetterDecoratorContextType: GenericType | undefined; var deferredGlobalClassSetterDecoratorContextType: GenericType | undefined; var deferredGlobalClassAccessorDecoratorContextType: GenericType | undefined; var deferredGlobalClassAccessorDecoratorTargetType: GenericType | undefined; var deferredGlobalClassAccessorDecoratorResultType: GenericType | undefined; var deferredGlobalClassFieldDecoratorContextType: GenericType | undefined; var allPotentiallyUnusedIdentifiers = new Map(); // key is file name var flowLoopStart = 0; var flowLoopCount = 0; var sharedFlowCount = 0; var flowAnalysisDisabled = false; var flowInvocationCount = 0; var lastFlowNode: FlowNode | undefined; var lastFlowNodeReachable: boolean; var flowTypeCache: Type[] | undefined; var contextualTypeNodes: Node[] = []; var contextualTypes: (Type | undefined)[] = []; var contextualIsCache: boolean[] = []; var contextualTypeCount = 0; var contextualBindingPatterns: BindingPattern[] = []; var inferenceContextNodes: Node[] = []; var inferenceContexts: (InferenceContext | undefined)[] = []; var inferenceContextCount = 0; var activeTypeMappers: TypeMapper[] = []; var activeTypeMappersCaches: Map[] = []; var activeTypeMappersCount = 0; var emptyStringType = getStringLiteralType(""); var zeroType = getNumberLiteralType(0); var zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); var resolutionTargets: TypeSystemEntity[] = []; var resolutionResults: boolean[] = []; var resolutionPropertyNames: TypeSystemPropertyName[] = []; var resolutionStart = 0; var inVarianceComputation = false; var suggestionCount = 0; var maximumSuggestionCount = 10; var mergedSymbols: Symbol[] = []; var symbolLinks: SymbolLinks[] = []; var nodeLinks: NodeLinks[] = []; var flowLoopCaches: Map[] = []; var flowLoopNodes: FlowNode[] = []; var flowLoopKeys: string[] = []; var flowLoopTypes: Type[][] = []; var sharedFlowNodes: FlowNode[] = []; var sharedFlowTypes: FlowType[] = []; var flowNodeReachable: (boolean | undefined)[] = []; var flowNodePostSuper: (boolean | undefined)[] = []; var potentialThisCollisions: Node[] = []; var potentialNewTargetCollisions: Node[] = []; var potentialWeakMapSetCollisions: Node[] = []; var potentialReflectCollisions: Node[] = []; var potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = []; var awaitedTypeStack: number[] = []; var reverseMappedSourceStack: Type[] = []; var reverseMappedTargetStack: Type[] = []; var reverseExpandingFlags = ExpandingFlags.None; var diagnostics = createDiagnosticCollection(); var suggestionDiagnostics = createDiagnosticCollection(); var typeofType = createTypeofType(); var _jsxNamespace: __String; var _jsxFactoryEntity: EntityName | undefined; var subtypeRelation = new Map(); var strictSubtypeRelation = new Map(); var assignableRelation = new Map(); var comparableRelation = new Map(); var identityRelation = new Map(); var enumRelation = new Map(); // Extensions suggested for path imports when module resolution is node16 or higher. // The first element of each tuple is the extension a file has. // The second element of each tuple is the extension that should be used in a path import. // e.g. if we want to import file `foo.mts`, we should write `import {} from "./foo.mjs". var suggestedExtensions: [string, string][] = [ [".mts", ".mjs"], [".ts", ".js"], [".cts", ".cjs"], [".mjs", ".mjs"], [".js", ".js"], [".cjs", ".cjs"], [".tsx", compilerOptions.jsx === JsxEmit.Preserve ? ".jsx" : ".js"], [".jsx", ".jsx"], [".json", ".json"], ]; /* eslint-enable no-var */ initializeTypeChecker(); return checker; function isDefinitelyReferenceToGlobalSymbolObject(node: Node): boolean { if (!isPropertyAccessExpression(node)) return false; if (!isIdentifier(node.name)) return false; if (!isPropertyAccessExpression(node.expression) && !isIdentifier(node.expression)) return false; if (isIdentifier(node.expression)) { // Exactly `Symbol.something` and `Symbol` either does not resolve or definitely resolves to the global Symbol return idText(node.expression) === "Symbol" && getResolvedSymbol(node.expression) === (getGlobalSymbol("Symbol" as __String, SymbolFlags.Value | SymbolFlags.ExportValue, /*diagnostic*/ undefined) || unknownSymbol); } if (!isIdentifier(node.expression.expression)) return false; // Exactly `globalThis.Symbol.something` and `globalThis` resolves to the global `globalThis` return idText(node.expression.name) === "Symbol" && idText(node.expression.expression) === "globalThis" && getResolvedSymbol(node.expression.expression) === globalThisSymbol; } function getCachedType(key: string | undefined) { return key ? cachedTypes.get(key) : undefined; } function setCachedType(key: string | undefined, type: Type) { if (key) cachedTypes.set(key, type); return type; } function getJsxNamespace(location: Node | undefined): __String { if (location) { const file = getSourceFileOfNode(location); if (file) { if (isJsxOpeningFragment(location)) { if (file.localJsxFragmentNamespace) { return file.localJsxFragmentNamespace; } const jsxFragmentPragma = file.pragmas.get("jsxfrag"); if (jsxFragmentPragma) { const chosenPragma = isArray(jsxFragmentPragma) ? jsxFragmentPragma[0] : jsxFragmentPragma; file.localJsxFragmentFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); visitNode(file.localJsxFragmentFactory, markAsSynthetic, isEntityName); if (file.localJsxFragmentFactory) { return file.localJsxFragmentNamespace = getFirstIdentifier(file.localJsxFragmentFactory).escapedText; } } const entity = getJsxFragmentFactoryEntity(location); if (entity) { file.localJsxFragmentFactory = entity; return file.localJsxFragmentNamespace = getFirstIdentifier(entity).escapedText; } } else { const localJsxNamespace = getLocalJsxNamespace(file); if (localJsxNamespace) { return file.localJsxNamespace = localJsxNamespace; } } } } if (!_jsxNamespace) { _jsxNamespace = "React" as __String; if (compilerOptions.jsxFactory) { _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); visitNode(_jsxFactoryEntity, markAsSynthetic); if (_jsxFactoryEntity) { _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; } } else if (compilerOptions.reactNamespace) { _jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace); } } if (!_jsxFactoryEntity) { _jsxFactoryEntity = factory.createQualifiedName(factory.createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement"); } return _jsxNamespace; } function getLocalJsxNamespace(file: SourceFile): __String | undefined { if (file.localJsxNamespace) { return file.localJsxNamespace; } const jsxPragma = file.pragmas.get("jsx"); if (jsxPragma) { const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); visitNode(file.localJsxFactory, markAsSynthetic, isEntityName); if (file.localJsxFactory) { return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; } } } function markAsSynthetic(node: T): VisitResult { setTextRangePosEnd(node, -1, -1); return visitEachChildWorker(node, markAsSynthetic, /*context*/ undefined); } function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken, skipDiagnostics?: boolean) { // Ensure we have all the type information in place for this file so that all the // emitter questions of this resolver will return the right information. if (!skipDiagnostics) getDiagnostics(sourceFile, cancellationToken); return emitResolver; } function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { const diagnostic = location ? createDiagnosticForNode(location, message, ...args) : createCompilerDiagnostic(message, ...args); const existing = diagnostics.lookup(diagnostic); if (existing) { return existing; } else { diagnostics.add(diagnostic); return diagnostic; } } function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { const diagnostic = error(location, message, ...args); diagnostic.skippedOn = key; return diagnostic; } function createError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { return location ? createDiagnosticForNode(location, message, ...args) : createCompilerDiagnostic(message, ...args); } function error(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { const diagnostic = createError(location, message, ...args); diagnostics.add(diagnostic); return diagnostic; } function getVerbatimModuleSyntaxErrorMessage(node: Node): DiagnosticMessage { const sourceFile = getSourceFileOfNode(node); const fileName = sourceFile.fileName; // Check if the file is .cts or .cjs (CommonJS-specific extensions) if (fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs])) { return Diagnostics.ECMAScript_imports_and_exports_cannot_be_written_in_a_CommonJS_file_under_verbatimModuleSyntax; } else { // For .ts, .tsx, .js, etc. return Diagnostics.ECMAScript_imports_and_exports_cannot_be_written_in_a_CommonJS_file_under_verbatimModuleSyntax_Adjust_the_type_field_in_the_nearest_package_json_to_make_this_file_an_ECMAScript_module_or_adjust_your_verbatimModuleSyntax_module_and_moduleResolution_settings_in_TypeScript; } } function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { if (isError) { diagnostics.add(diagnostic); } else { suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); } } function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, ...args: DiagnosticArguments): void { // Pseudo-synthesized input node if (location.pos 1) { return parentSymbol.flags & SymbolFlags.Interface ? some(symbol.declarations, isDeprecatedDeclaration) : every(symbol.declarations, isDeprecatedDeclaration); } return !!symbol.valueDeclaration && isDeprecatedDeclaration(symbol.valueDeclaration) || length(symbol.declarations) && every(symbol.declarations, isDeprecatedDeclaration); } function isDeprecatedDeclaration(declaration: Declaration) { return !!(getCombinedNodeFlagsCached(declaration) & NodeFlags.Deprecated); } function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); return addDeprecatedSuggestionWorker(declarations, diagnostic); } function addDeprecatedSuggestionWithSignature(location: Node, declaration: Node, deprecatedEntity: string | undefined, signatureString: string) { const diagnostic = deprecatedEntity ? createDiagnosticForNode(location, Diagnostics.The_signature_0_of_1_is_deprecated, signatureString, deprecatedEntity) : createDiagnosticForNode(location, Diagnostics._0_is_deprecated, signatureString); return addDeprecatedSuggestionWorker(declaration, diagnostic); } function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { symbolCount++; const symbol = new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol; symbol.links = new SymbolLinks() as TransientSymbolLinks; symbol.links.checkFlags = checkFlags || CheckFlags.None; return symbol; } function createParameter(name: __String, type: Type) { const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name); symbol.links.type = type; return symbol; } function createProperty(name: __String, type: Type) { const symbol = createSymbol(SymbolFlags.Property, name); symbol.links.type = type; return symbol; } function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { let result: SymbolFlags = 0; if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes; if (flags & SymbolFlags.FunctionScopedVariable) result |= SymbolFlags.FunctionScopedVariableExcludes; if (flags & SymbolFlags.Property) result |= SymbolFlags.PropertyExcludes; if (flags & SymbolFlags.EnumMember) result |= SymbolFlags.EnumMemberExcludes; if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes; if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes; if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes; if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes; if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes; if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes; if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes; if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes; if (flags & SymbolFlags.SetAccessor) result |= SymbolFlags.SetAccessorExcludes; if (flags & SymbolFlags.TypeParameter) result |= SymbolFlags.TypeParameterExcludes; if (flags & SymbolFlags.TypeAlias) result |= SymbolFlags.TypeAliasExcludes; if (flags & SymbolFlags.Alias) result |= SymbolFlags.AliasExcludes; return result; } function recordMergedSymbol(target: Symbol, source: Symbol) { if (!source.mergeId) { source.mergeId = nextMergeId; nextMergeId++; } mergedSymbols[source.mergeId] = target; } function cloneSymbol(symbol: Symbol): TransientSymbol { const result = createSymbol(symbol.flags, symbol.escapedName); result.declarations = symbol.declarations ? symbol.declarations.slice() : []; result.parent = symbol.parent; if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; if (symbol.members) result.members = new Map(symbol.members); if (symbol.exports) result.exports = new Map(symbol.exports); recordMergedSymbol(result, symbol); return result; } /** * Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it. * If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it. */ function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol { if ( !(target.flags & getExcludedSymbolFlags(source.flags)) || (source.flags | target.flags) & SymbolFlags.Assignment ) { if (source === target) { // This can happen when an export assigned namespace exports something also erroneously exported at the top level // See `declarationFileNoCrashOnExtraExportModifier` for an example return target; } if (!(target.flags & SymbolFlags.Transient)) { const resolvedTarget = resolveSymbol(target); if (resolvedTarget === unknownSymbol) { return source; } if ( !(resolvedTarget.flags & getExcludedSymbolFlags(source.flags)) || (source.flags | resolvedTarget.flags) & SymbolFlags.Assignment ) { target = cloneSymbol(resolvedTarget); } else { reportMergeSymbolError(target, source); return source; } } // Javascript static-property-assignment declarations always merge, even though they are also values if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) { // reset flag when merging instantiated module into value module that has only const enums target.constEnumOnlyModule = false; } target.flags |= source.flags; if (source.valueDeclaration) { setValueDeclaration(target, source.valueDeclaration); } addRange(target.declarations, source.declarations); if (source.members) { if (!target.members) target.members = createSymbolTable(); mergeSymbolTable(target.members, source.members, unidirectional); } if (source.exports) { if (!target.exports) target.exports = createSymbolTable(); mergeSymbolTable(target.exports, source.exports, unidirectional, target); } if (!unidirectional) { recordMergedSymbol(target, source); } } else if (target.flags & SymbolFlags.NamespaceModule) { // Do not report an error when merging `var globalThis` with the built-in `globalThis`, // as we will already report a "Declaration name conflicts..." error, and this error // won't make much sense. if (target !== globalThisSymbol) { error( source.declarations && getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target), ); } } else { reportMergeSymbolError(target, source); } return target; function reportMergeSymbolError(target: Symbol, source: Symbol) { const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum); const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable); const message = isEitherEnum ? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations : isEitherBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0; const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]); const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]); const isSourcePlainJs = isPlainJsFile(sourceSymbolFile, compilerOptions.checkJs); const isTargetPlainJs = isPlainJsFile(targetSymbolFile, compilerOptions.checkJs); const symbolName = symbolToString(source); // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, (): DuplicateInfoForFiles => ({ firstFile, secondFile, conflictingSymbols: new Map() })); const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, (): DuplicateInfoForSymbol => ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] })); if (!isSourcePlainJs) addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); if (!isTargetPlainJs) addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); } else { if (!isSourcePlainJs) addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); if (!isTargetPlainJs) addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); } } function addDuplicateLocations(locs: Declaration[], symbol: Symbol): void { if (symbol.declarations) { for (const decl of symbol.declarations) { pushIfUnique(locs, decl); } } } } function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) { forEach(target.declarations, node => { addDuplicateDeclarationError(node, message, symbolName, source.declarations); }); } function addDuplicateDeclarationError(node: Declaration, message: DiagnosticMessage, symbolName: string, relatedNodes: readonly Declaration[] | undefined) { const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node; const err = lookupOrIssueError(errorNode, message, symbolName); for (const relatedNode of relatedNodes || emptyArray) { const adjustedNode = (getExpandoInitializer(relatedNode, /*isPrototypeAssignment*/ false) ? getNameOfExpando(relatedNode) : getNameOfDeclaration(relatedNode)) || relatedNode; if (adjustedNode === errorNode) continue; err.relatedInformation = err.relatedInformation || []; const leadingMessage = createDiagnosticForNode(adjustedNode, Diagnostics._0_was_also_declared_here, symbolName); const followOnMessage = createDiagnosticForNode(adjustedNode, Diagnostics.and_here); if (length(err.relatedInformation) >= 5 || some(err.relatedInformation, r => compareDiagnostics(r, followOnMessage) === Comparison.EqualTo || compareDiagnostics(r, leadingMessage) === Comparison.EqualTo)) continue; addRelatedInfo(err, !length(err.relatedInformation) ? leadingMessage : followOnMessage); } } function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined { if (!first?.size) return second; if (!second?.size) return first; const combined = createSymbolTable(); mergeSymbolTable(combined, first); mergeSymbolTable(combined, second); return combined; } function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false, mergedParent?: Symbol) { source.forEach((sourceSymbol, id) => { const targetSymbol = target.get(id); const merged = targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : getMergedSymbol(sourceSymbol); if (mergedParent && targetSymbol) { // If a merge was performed on the target symbol, set its parent to the merged parent that initiated the merge // of its exports. Otherwise, `merged` came only from `sourceSymbol` and can keep its parent: // // // a.ts // export interface A { x: number; } // // // b.ts // declare module "./a" { // interface A { y: number; } // interface B {} // } // // When merging the module augmentation into a.ts, the symbol for `A` will itself be merged, so its parent // should be the merged module symbol. But the symbol for `B` has only one declaration, so its parent should // be the module augmentation symbol, which contains its only declaration. merged.parent = mergedParent; } target.set(id, merged); }); } function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { const moduleAugmentation = moduleName.parent as ModuleDeclaration; if (moduleAugmentation.symbol.declarations?.[0] !== moduleAugmentation) { // this is a combined symbol for multiple augmentations within the same file. // its symbol already has accumulated information for all declarations // so we need to add it just once - do the work only for first declaration Debug.assert(moduleAugmentation.symbol.declarations!.length > 1); return; } if (isGlobalScopeAugmentation(moduleAugmentation)) { mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); } else { // find a module that about to be augmented // do not validate names of augmentations that are defined in ambient context const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient) ? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found : undefined; let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*ignoreErrors*/ false, /*isForAugmentation*/ true); if (!mainModule) { return; } // obtain item referenced by 'export=' mainModule = resolveExternalModuleSymbol(mainModule); if (mainModule.flags & SymbolFlags.Namespace) { // If we're merging an augmentation to a pattern ambient module, we want to // perform the merge unidirectionally from the augmentation ('a.foo') to // the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you // all the exports both from the pattern and from the augmentation, but // 'getMergedSymbol()' on *.foo only gives you exports from *.foo. if (some(patternAmbientModules, module => mainModule === module.symbol)) { const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true); if (!patternAmbientModuleAugmentations) { patternAmbientModuleAugmentations = new Map(); } // moduleName will be a StringLiteral since this is not `declare global`. patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged); } else { if (mainModule.exports?.get(InternalSymbolName.ExportStar) && moduleAugmentation.symbol.exports?.size) { // We may need to merge the module augmentation's exports into the target symbols of the resolved exports const resolvedExports = getResolvedMembersOrExportsOfSymbol(mainModule, MembersOrExportsResolutionKind.resolvedExports); for (const [key, value] of arrayFrom(moduleAugmentation.symbol.exports.entries())) { if (resolvedExports.has(key) && !mainModule.exports.has(key)) { mergeSymbol(resolvedExports.get(key)!, value); } } } mergeSymbol(mainModule, moduleAugmentation.symbol); } } else { // moduleName will be a StringLiteral since this is not `declare global`. error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, (moduleName as StringLiteral).text); } } } function addUndefinedToGlobalsOrErrorOnRedeclaration() { const name = undefinedSymbol.escapedName; const targetSymbol = globals.get(name); if (targetSymbol) { forEach(targetSymbol.declarations, declaration => { // checkTypeNameIsReserved will have added better diagnostics for type declarations. if (!isTypeDeclaration(declaration)) { diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, unescapeLeadingUnderscores(name))); } }); } else { globals.set(name, undefinedSymbol); } } function getSymbolLinks(symbol: Symbol): SymbolLinks { if (symbol.flags & SymbolFlags.Transient) return (symbol as TransientSymbol).links; const id = getSymbolId(symbol); return symbolLinks[id] ??= new SymbolLinks(); } function getNodeLinks(node: Node): NodeLinks { const nodeId = getNodeId(node); return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks as any)()); } function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined { if (meaning) { const symbol = getMergedSymbol(symbols.get(name)); if (symbol) { if (symbol.flags & meaning) { return symbol; } if (symbol.flags & SymbolFlags.Alias) { const targetFlags = getSymbolFlags(symbol); // `targetFlags` will be `SymbolFlags.All` if an error occurred in alias resolution; this avoids cascading errors if (targetFlags & meaning) { return symbol; } } } } // return undefined if we can't find a symbol. } /** * Get symbols that represent parameter-property-declaration as parameter and as property declaration * @param parameter a parameterDeclaration node * @param parameterName a name of the parameter to get the symbols for. * @return a tuple of two symbols */ function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterPropertyDeclaration, parameterName: __String): [Symbol, Symbol] { const constructorDeclaration = parameter.parent; const classDeclaration = parameter.parent.parent; const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value); const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); if (parameterSymbol && propertySymbol) { return [parameterSymbol, propertySymbol]; } return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); } function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { const declarationFile = getSourceFileOfNode(declaration); const useFile = getSourceFileOfNode(usage); const declContainer = getEnclosingBlockScopeContainer(declaration); if (declarationFile !== useFile) { if ( (moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || (!compilerOptions.outFile) || isInTypeQuery(usage) || declaration.flags & NodeFlags.Ambient ) { // nodes are in different files and order cannot be determined return true; } // declaration is after usage // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { return true; } const sourceFiles = host.getSourceFiles(); return sourceFiles.indexOf(declarationFile) A.x) class A { ... }`) const container = findAncestor(usage, n => n === declaration ? "quit" : isComputedPropertyName(n) ? n.parent.parent === declaration : !legacyDecorators && isDecorator(n) && (n.parent === declaration || isMethodDeclaration(n.parent) && n.parent.parent === declaration || isGetOrSetAccessorDeclaration(n.parent) && n.parent.parent === declaration || isPropertyDeclaration(n.parent) && n.parent.parent === declaration || isParameter(n.parent) && n.parent.parent.parent === declaration)); if (!container) { return true; } if (!legacyDecorators && isDecorator(container)) { return !!findAncestor(usage, n => n === container ? "quit" : isFunctionLike(n) && !getImmediatelyInvokedFunctionExpression(n)); } return false; } else if (isPropertyDeclaration(declaration)) { // still might be illegal if a self-referencing property initializer (eg private x = this.x) return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false); } else if (isParameterPropertyDeclaration(declaration, declaration.parent)) { // foo = this.bar is illegal in emitStandardClassFields when bar is a parameter property return !(emitStandardClassFields && getContainingClass(declaration) === getContainingClass(usage) && isUsedInFunctionOrInstanceProperty(usage, declaration)); } return true; } // declaration is after usage, but it can still be legal if usage is deferred: // 1. inside an export specifier // 2. inside a function // 3. inside an instance property initializer, a reference to a non-instance property // (except when emitStandardClassFields: true and the reference is to a parameter property) // 4. inside a static property initializer, a reference to a static method in the same class // 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ) if (usage.parent.kind === SyntaxKind.ExportSpecifier || (usage.parent.kind === SyntaxKind.ExportAssignment && (usage.parent as ExportAssignment).isExportEquals)) { // export specifiers do not use the variable, they only make it available for use return true; } // When resolving symbols for exports, the `usage` location passed in can be the export site directly if (usage.kind === SyntaxKind.ExportAssignment && (usage as ExportAssignment).isExportEquals) { return true; } if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { if ( emitStandardClassFields && getContainingClass(declaration) && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent)) ) { return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); } else { return true; } } return false; function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { switch (declaration.parent.parent.kind) { case SyntaxKind.VariableStatement: case SyntaxKind.ForStatement: case SyntaxKind.ForOfStatement: // variable statement/for/for-of statement case, // use site should not be inside variable declaration (initializer of declaration or binding element) if (isSameScopeDescendentOf(usage, declaration, declContainer)) { return true; } break; } // ForIn/ForOf case - use site should not be used in expression part const grandparent = declaration.parent.parent; return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, declContainer); } function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node) { return isUsedInFunctionOrInstancePropertyWorker(usage, declaration); } function isUsedInFunctionOrInstancePropertyWorker(usage: Node, declaration: Node): boolean { return !!findAncestor(usage, current => { if (current === declContainer) { return "quit"; } if (isFunctionLike(current)) { return !getImmediatelyInvokedFunctionExpression(current); } if (isClassStaticBlockDeclaration(current)) { return declaration.pos declaration.end) { return false; } // still might be legal if usage is deferred (e.g. x: any = () => this.x) // otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x) const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => { if (node === declaration) { return "quit"; } switch (node.kind) { case SyntaxKind.ArrowFunction: return true; case SyntaxKind.PropertyDeclaration: // even when stopping at any property declaration, they need to come from the same class return stopAtAnyPropertyDeclaration && (isPropertyDeclaration(declaration) && node.parent === declaration.parent || isParameterPropertyDeclaration(declaration, declaration.parent) && node.parent === declaration.parent.parent) ? "quit" : true; case SyntaxKind.Block: switch (node.parent.kind) { case SyntaxKind.GetAccessor: case SyntaxKind.MethodDeclaration: case SyntaxKind.SetAccessor: return true; default: return false; } default: return false; } }); return ancestorChangingReferenceScope === undefined; } } function getRequiresScopeChangeCache(node: FunctionLikeDeclaration) { return getNodeLinks(node).declarationRequiresScopeChange; } function setRequiresScopeChangeCache(node: FunctionLikeDeclaration, value: boolean) { getNodeLinks(node).declarationRequiresScopeChange = value; } // The invalid initializer error is needed in two situation: // 1. When result is undefined, after checking for a missing "this." // 2. When result is defined function checkAndReportErrorForInvalidInitializer(errorLocation: Node | undefined, name: __String, propertyWithInvalidInitializer: PropertyDeclaration, result: Symbol | undefined) { if (!emitStandardClassFields) { if (errorLocation && !result && checkAndReportErrorForMissingPrefix(errorLocation, name, name)) { return true; } // We have a match, but the reference occurred within a property initializer and the identifier also binds // to a local variable in the constructor where the code will be emitted. Note that this is actually allowed // with emitStandardClassFields because the scope semantics are different. error( errorLocation, errorLocation && propertyWithInvalidInitializer.type && textRangeContainsPositionInclusive(propertyWithInvalidInitializer.type, errorLocation.pos) ? Diagnostics.Type_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor : Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, declarationNameToString(propertyWithInvalidInitializer.name), diagnosticName(name), ); return true; } return false; } function onFailedToResolveSymbol( errorLocation: Node | undefined, nameArg: __String | Identifier, meaning: SymbolFlags, nameNotFoundMessage: DiagnosticMessage, ) { const name = isString(nameArg) ? nameArg : (nameArg as Identifier).escapedText; addLazyDiagnostic(() => { if ( !errorLocation || errorLocation.parent.kind !== SyntaxKind.JSDocLink && !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg) && !checkAndReportErrorForExtendingInterface(errorLocation) && !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && !checkAndReportErrorForExportingPrimitiveType(errorLocation, name) && !checkAndReportErrorForUsingNamespaceAsTypeOrValue(errorLocation, name, meaning) && !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning) ) { let suggestion: Symbol | undefined; let suggestedLib: string | undefined; // Report missing lib first if (nameArg) { suggestedLib = getSuggestedLibForNonExistentName(nameArg); if (suggestedLib) { error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), suggestedLib); } } // then spelling suggestions if (!suggestedLib && suggestionCount { const name = result.escapedName; const isInExternalModule = lastLocation && isSourceFile(lastLocation) && isExternalOrCommonJsModule(lastLocation); // Only check for block-scoped variable if we have an error location and are looking for the // name with variable meaning // For example, // declare module foo { // interface bar {} // } // const foo/*1*/: foo/*2*/.bar; // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: // block-scoped variable and namespace module. However, only when we // try to resolve name in /*1*/ which is used in variable position, // we want to check for block-scoped if ( errorLocation && (meaning & SymbolFlags.BlockScopedVariable || ((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value)) ) { const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result); if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) { checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation); } } // If we're in an external module, we can't reference value symbols created from UMD export declarations if (isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(errorLocation!.flags & NodeFlags.JSDoc)) { const merged = getMergedSymbol(result); if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) { errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name)); } } // If we're in a parameter initializer or binding name, we can't reference the values of the parameter whose initializer we're within or parameters to the right if (associatedDeclarationForContainingInitializerOrBindingName && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { const candidate = getMergedSymbol(getLateBoundSymbol(result)); const root = getRootDeclaration(associatedDeclarationForContainingInitializerOrBindingName) as ParameterDeclaration; // A parameter initializer or binding pattern initializer within a parameter cannot refer to itself if (candidate === getSymbolOfDeclaration(associatedDeclarationForContainingInitializerOrBindingName)) { error(errorLocation, Diagnostics.Parameter_0_cannot_reference_itself, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name)); } // And it cannot refer to any declarations which come after it else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos && root.parent.locals && getSymbol(root.parent.locals, candidate.escapedName, meaning) === candidate) { error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); } } if (errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias && !(result.flags & SymbolFlags.Value) && !isValidTypeOnlyAliasUseSite(errorLocation)) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(result, SymbolFlags.Value); if (typeOnlyDeclaration) { const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport ? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type : Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type; const unescapedName = unescapeLeadingUnderscores(name); addTypeOnlyDeclarationRelatedInfo( error(errorLocation, message, unescapedName), typeOnlyDeclaration, unescapedName, ); } } // Look at 'compilerOptions.isolatedModules' and not 'getIsolatedModules(...)' (which considers 'verbatimModuleSyntax') // here because 'verbatimModuleSyntax' will already have an error for importing a type without 'import type'. if (compilerOptions.isolatedModules && result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { const isGlobal = getSymbol(globals, name, meaning) === result; const nonValueSymbol = isGlobal && isSourceFile(lastLocation) && lastLocation.locals && getSymbol(lastLocation.locals, name, ~SymbolFlags.Value); if (nonValueSymbol) { const importDecl = nonValueSymbol.declarations?.find(d => d.kind === SyntaxKind.ImportSpecifier || d.kind === SyntaxKind.ImportClause || d.kind === SyntaxKind.NamespaceImport || d.kind === SyntaxKind.ImportEqualsDeclaration); if (importDecl && !isTypeOnlyImportDeclaration(importDecl)) { error(importDecl, Diagnostics.Import_0_conflicts_with_global_value_used_in_this_file_so_must_be_declared_with_a_type_only_import_when_isolatedModules_is_enabled, unescapeLeadingUnderscores(name)); } } } }); } function addTypeOnlyDeclarationRelatedInfo(diagnostic: Diagnostic, typeOnlyDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, unescapedName: string) { if (!typeOnlyDeclaration) return diagnostic; return addRelatedInfo( diagnostic, createDiagnosticForNode( typeOnlyDeclaration, typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here, unescapedName, ), ); } function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) { return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); } function checkAndReportErrorForMissingPrefix(errorLocation: Node, name: __String, nameArg: __String | Identifier): boolean { if (!isIdentifier(errorLocation) || errorLocation.escapedText !== name || isTypeReferenceIdentifier(errorLocation) || isInTypeQuery(errorLocation)) { return false; } const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); let location: Node = container; while (location) { if (isClassLike(location.parent)) { const classSymbol = getSymbolOfDeclaration(location.parent); if (!classSymbol) { break; } // Check to see if a static member exists. const constructorType = getTypeOfSymbol(classSymbol); if (getPropertyOfType(constructorType, name)) { error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0, diagnosticName(nameArg), symbolToString(classSymbol)); return true; } // No static member is present. // Check if we're in an instance method and look for a relevant instance member. if (location === container && !isStatic(location)) { const instanceType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType!; // TODO: GH#18217 if (getPropertyOfType(instanceType, name)) { error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, diagnosticName(nameArg)); return true; } } } location = location.parent; } return false; } function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean { const expression = getEntityNameForExtendingInterface(errorLocation); if (expression && resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) { error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression)); return true; } return false; } /** * Climbs up parents to an ExpressionWithTypeArguments, and returns its expression, * but returns undefined if that expression is not an EntityNameExpression. */ function getEntityNameForExtendingInterface(node: Node): EntityNameExpression | undefined { switch (node.kind) { case SyntaxKind.Identifier: case SyntaxKind.PropertyAccessExpression: return node.parent ? getEntityNameForExtendingInterface(node.parent) : undefined; case SyntaxKind.ExpressionWithTypeArguments: if (isEntityNameExpression((node as ExpressionWithTypeArguments).expression)) { return (node as ExpressionWithTypeArguments).expression as EntityNameExpression; } // falls through default: return undefined; } } function checkAndReportErrorForUsingTypeAsNamespace(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(errorLocation) ? SymbolFlags.Value : 0); if (meaning === namespaceMeaning) { const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~namespaceMeaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false)); const parent = errorLocation.parent; if (symbol) { if (isQualifiedName(parent)) { Debug.assert(parent.left === errorLocation, "Should only be resolving left side of qualified name as a namespace"); const propName = parent.right.escapedText; const propType = getPropertyOfType(getDeclaredTypeOfSymbol(symbol), propName); if (propType) { error( parent, Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, unescapeLeadingUnderscores(name), unescapeLeadingUnderscores(propName), ); return true; } } error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_namespace_here, unescapeLeadingUnderscores(name)); return true; } } return false; } function checkAndReportErrorForUsingValueAsType(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { if (meaning & (SymbolFlags.Type & ~SymbolFlags.Namespace)) { const symbol = resolveSymbol(resolveName(errorLocation, name, ~SymbolFlags.Type & SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ false)); if (symbol && !(symbol.flags & SymbolFlags.Namespace)) { error(errorLocation, Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, unescapeLeadingUnderscores(name)); return true; } } return false; } function isPrimitiveTypeName(name: __String) { return name === "any" || name === "string" || name === "number" || name === "boolean" || name === "never" || name === "unknown"; } function checkAndReportErrorForExportingPrimitiveType(errorLocation: Node, name: __String): boolean { if (isPrimitiveTypeName(name) && errorLocation.parent.kind === SyntaxKind.ExportSpecifier) { error(errorLocation, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, name as string); return true; } return false; } function checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { if (meaning & SymbolFlags.Value) { if (isPrimitiveTypeName(name)) { const grandparent = errorLocation.parent.parent; if (grandparent && grandparent.parent && isHeritageClause(grandparent)) { const heritageKind = grandparent.token; const containerKind = grandparent.parent.kind; if (containerKind === SyntaxKind.InterfaceDeclaration && heritageKind === SyntaxKind.ExtendsKeyword) { error(errorLocation, Diagnostics.An_interface_cannot_extend_a_primitive_type_like_0_It_can_only_extend_other_named_object_types, unescapeLeadingUnderscores(name)); } else if (isClassLike(grandparent.parent) && heritageKind === SyntaxKind.ExtendsKeyword) { error(errorLocation, Diagnostics.A_class_cannot_extend_a_primitive_type_like_0_Classes_can_only_extend_constructable_values, unescapeLeadingUnderscores(name)); } else if (isClassLike(grandparent.parent) && heritageKind === SyntaxKind.ImplementsKeyword) { error(errorLocation, Diagnostics.A_class_cannot_implement_a_primitive_type_like_0_It_can_only_implement_other_named_object_types, unescapeLeadingUnderscores(name)); } } else { error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name)); } return true; } const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ false)); const allFlags = symbol && getSymbolFlags(symbol); if (symbol && allFlags !== undefined && !(allFlags & SymbolFlags.Value)) { const rawName = unescapeLeadingUnderscores(name); if (isES2015OrLaterConstructorName(name)) { error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later, rawName); } else if (maybeMappedType(errorLocation, symbol)) { error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Did_you_mean_to_use_1_in_0, rawName, rawName === "K" ? "P" : "K"); } else { error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, rawName); } return true; } } return false; } function maybeMappedType(node: Node, symbol: Symbol) { const container = findAncestor(node.parent, n => isComputedPropertyName(n) || isPropertySignature(n) ? false : isTypeLiteralNode(n) || "quit") as TypeLiteralNode | undefined; if (container && container.members.length === 1) { const type = getDeclaredTypeOfSymbol(symbol); return !!(type.flags & TypeFlags.Union) && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true); } return false; } function isES2015OrLaterConstructorName(n: __String) { switch (n) { case "Promise": case "Symbol": case "Map": case "WeakMap": case "Set": case "WeakSet": return true; } return false; } function checkAndReportErrorForUsingNamespaceAsTypeOrValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { if (meaning & (SymbolFlags.Value & ~SymbolFlags.Type)) { const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule, /*nameNotFoundMessage*/ undefined, /*isUse*/ false)); if (symbol) { error( errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_value, unescapeLeadingUnderscores(name), ); return true; } } else if (meaning & (SymbolFlags.Type & ~SymbolFlags.Value)) { const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Module, /*nameNotFoundMessage*/ undefined, /*isUse*/ false)); if (symbol) { error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); return true; } } return false; } function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void { Debug.assert(!!(result.flags & SymbolFlags.BlockScopedVariable || result.flags & SymbolFlags.Class || result.flags & SymbolFlags.Enum)); if (result.flags & (SymbolFlags.Function | SymbolFlags.FunctionScopedVariable | SymbolFlags.Assignment) && result.flags & SymbolFlags.Class) { // constructor functions aren't block scoped return; } // Block-scoped variables cannot be used before their definition const declaration = result.declarations?.find( d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration), ); if (declaration === undefined) return Debug.fail("checkResolvedBlockScopedVariable could not find block-scoped declaration"); if (!(declaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) { let diagnosticMessage; const declarationName = declarationNameToString(getNameOfDeclaration(declaration)); if (result.flags & SymbolFlags.BlockScopedVariable) { diagnosticMessage = error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName); } else if (result.flags & SymbolFlags.Class) { diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName); } else if (result.flags & SymbolFlags.RegularEnum) { diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); } else { Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); if (getIsolatedModules(compilerOptions)) { diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); } } if (diagnosticMessage) { addRelatedInfo(diagnosticMessage, createDiagnosticForNode(declaration, Diagnostics._0_is_declared_here, declarationName)); } } } /* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached. * If at any point current node is equal to 'parent' node - return true. * If current node is an IIFE, continue walking up. * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. */ function isSameScopeDescendentOf(initial: Node, parent: Node | undefined, stopAt: Node): boolean { return !!parent && !!findAncestor(initial, n => n === parent || (n === stopAt || isFunctionLike(n) && (!getImmediatelyInvokedFunctionExpression(n) || (getFunctionFlags(n) & FunctionFlags.AsyncGenerator)) ? "quit" : false)); } function getAnyImportSyntax(node: Node): AnyImportOrJsDocImport | undefined { switch (node.kind) { case SyntaxKind.ImportEqualsDeclaration: return node as ImportEqualsDeclaration; case SyntaxKind.ImportClause: return (node as ImportClause).parent; case SyntaxKind.NamespaceImport: return (node as NamespaceImport).parent.parent; case SyntaxKind.ImportSpecifier: return (node as ImportSpecifier).parent.parent.parent; default: return undefined; } } function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration | undefined { return symbol.declarations && findLast(symbol.declarations, isAliasSymbolDeclaration); } /** * An alias symbol is created by one of the following declarations: * import = ... * import from ... * import * as from ... * import { x as } from ... * export { x as } from ... * export * as ns from ... * export = * export default * module.exports = * {} * {name: } * const { x } = require ... */ function isAliasSymbolDeclaration(node: Node): boolean { return node.kind === SyntaxKind.ImportEqualsDeclaration || node.kind === SyntaxKind.NamespaceExportDeclaration || node.kind === SyntaxKind.ImportClause && !!(node as ImportClause).name || node.kind === SyntaxKind.NamespaceImport || node.kind === SyntaxKind.NamespaceExport || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.ExportSpecifier || node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment) || isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) || isAccessExpression(node) && isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAliasableOrJsExpression(node.parent.right) || node.kind === SyntaxKind.ShorthandPropertyAssignment || node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer) || node.kind === SyntaxKind.VariableDeclaration && isVariableDeclarationInitializedToBareOrAccessedRequire(node) || node.kind === SyntaxKind.BindingElement && isVariableDeclarationInitializedToBareOrAccessedRequire(node.parent.parent); } function isAliasableOrJsExpression(e: Expression) { return isAliasableExpression(e) || isFunctionExpression(e) && isJSConstructor(e); } function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration | VariableDeclaration, dontResolveAlias: boolean): Symbol | undefined { const commonJSPropertyAccess = getCommonJSPropertyAccess(node); if (commonJSPropertyAccess) { const name = (getLeftmostAccessExpression(commonJSPropertyAccess.expression) as CallExpression).arguments[0] as StringLiteral; return isIdentifier(commonJSPropertyAccess.name) ? resolveSymbol(getPropertyOfType(resolveExternalModuleTypeByLiteral(name), commonJSPropertyAccess.name.escapedText)) : undefined; } if (isVariableDeclaration(node) || node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { const immediate = resolveExternalModuleName( node, getExternalModuleRequireArgument(node) || getExternalModuleImportEqualsDeclarationExpression(node), ); const resolved = resolveExternalModuleSymbol(immediate); if (resolved && ModuleKind.Node20 the `module.exports` export value // // But without `esModuleInterop`, it will be broken: // // const dep_1 = require("./dep.mjs"); // the `module.exports` export value (could be primitive) // dep_1.default; // `default` property access on the `module.exports` export value // // We could try to resolve the 'default' property in the latter case, but it's a mistake to run in this // environment without `esModuleInterop`, so just error. if (!getESModuleInterop(compilerOptions)) { error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), "esModuleInterop"); return undefined; } markSymbolOfAliasDeclarationIfTypeOnly(node, exportModuleDotExportsSymbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ false); return exportModuleDotExportsSymbol; } else { exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias); } if (!specifier) { return exportDefaultSymbol; } const hasDefaultOnly = isOnlyImportableAsDefault(specifier, moduleSymbol); const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, specifier); if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) { if (hasExportAssignmentSymbol(moduleSymbol) && !allowSyntheticDefaultImports) { const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); const exportAssignment = exportEqualsSymbol!.valueDeclaration; const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName); if (exportAssignment) { addRelatedInfo( err, createDiagnosticForNode( exportAssignment, Diagnostics.This_module_is_declared_with_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag, compilerOptionName, ), ); } } else if (isImportClause(node)) { reportNonDefaultExport(moduleSymbol, node); } else { errorNoModuleMemberSymbol(moduleSymbol, moduleSymbol, node, isImportOrExportSpecifier(node) && node.propertyName || node.name); } } else if (hasSyntheticDefault || hasDefaultOnly) { // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteEmpty*/ false); return resolved; } markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ false); return exportDefaultSymbol; } function getModuleSpecifierForImportOrExport(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportOrExportSpecifier): Expression | undefined { switch (node.kind) { case SyntaxKind.ImportClause: return node.parent.moduleSpecifier; case SyntaxKind.ImportEqualsDeclaration: return isExternalModuleReference(node.moduleReference) ? node.moduleReference.expression : undefined; case SyntaxKind.NamespaceImport: return node.parent.parent.moduleSpecifier; case SyntaxKind.ImportSpecifier: return node.parent.parent.parent.moduleSpecifier; case SyntaxKind.ExportSpecifier: return node.parent.parent.moduleSpecifier; default: return Debug.assertNever(node); } } function reportNonDefaultExport(moduleSymbol: Symbol, node: ImportClause) { if (moduleSymbol.exports?.has(node.symbol.escapedName)) { error( node.name, Diagnostics.Module_0_has_no_default_export_Did_you_mean_to_use_import_1_from_0_instead, symbolToString(moduleSymbol), symbolToString(node.symbol), ); } else { const diagnostic = error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); const exportStar = moduleSymbol.exports?.get(InternalSymbolName.ExportStar); if (exportStar) { const defaultExport = exportStar.declarations?.find(decl => !!( isExportDeclaration(decl) && decl.moduleSpecifier && resolveExternalModuleName(decl, decl.moduleSpecifier)?.exports?.has(InternalSymbolName.Default) ) ); if (defaultExport) { addRelatedInfo(diagnostic, createDiagnosticForNode(defaultExport, Diagnostics.export_Asterisk_does_not_re_export_a_default)); } } } } function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { const moduleSpecifier = node.parent.parent.moduleSpecifier; const immediate = resolveExternalModuleName(node, moduleSpecifier); const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressInteropError*/ false); markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); return resolved; } function getTargetOfNamespaceExport(node: NamespaceExport, dontResolveAlias: boolean): Symbol | undefined { const moduleSpecifier = node.parent.moduleSpecifier; const immediate = moduleSpecifier && resolveExternalModuleName(node, moduleSpecifier); const resolved = moduleSpecifier && resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressInteropError*/ false); markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); return resolved; } // This function creates a synthetic symbol that combines the value side of one symbol with the // type/namespace side of another symbol. Consider this example: // // declare module graphics { // interface Point { // x: number; // y: number; // } // } // declare var graphics: { // Point: new (x: number, y: number) => graphics.Point; // } // declare module "graphics" { // export = graphics; // } // // An 'import { Point } from "graphics"' needs to create a symbol that combines the value side 'Point' // property with the type/namespace side interface 'Point'. function combineValueAndTypeSymbols(valueSymbol: Symbol, typeSymbol: Symbol): Symbol { if (valueSymbol === unknownSymbol && typeSymbol === unknownSymbol) { return unknownSymbol; } if (valueSymbol.flags & (SymbolFlags.Type | SymbolFlags.Namespace)) { return valueSymbol; } const result = createSymbol(valueSymbol.flags | typeSymbol.flags, valueSymbol.escapedName); Debug.assert(valueSymbol.declarations || typeSymbol.declarations); result.declarations = deduplicate(concatenate(valueSymbol.declarations!, typeSymbol.declarations), equateValues); result.parent = valueSymbol.parent || typeSymbol.parent; if (valueSymbol.valueDeclaration) result.valueDeclaration = valueSymbol.valueDeclaration; if (typeSymbol.members) result.members = new Map(typeSymbol.members); if (valueSymbol.exports) result.exports = new Map(valueSymbol.exports); return result; } function getExportOfModule(symbol: Symbol, nameText: __String, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { if (symbol.flags & SymbolFlags.Module) { const exportSymbol = getExportsOfSymbol(symbol).get(nameText); const resolved = resolveSymbol(exportSymbol, dontResolveAlias); const exportStarDeclaration = getSymbolLinks(symbol).typeOnlyExportStarMap?.get(nameText); markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false, exportStarDeclaration, nameText); return resolved; } } function getPropertyOfVariable(symbol: Symbol, name: __String): Symbol | undefined { if (symbol.flags & SymbolFlags.Variable) { const typeAnnotation = (symbol.valueDeclaration as VariableDeclaration).type; if (typeAnnotation) { return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name)); } } } function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration | JSDocImportTag, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined { const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration | JSDocImportTag).moduleSpecifier!; const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; if (!isIdentifier(name) && name.kind !== SyntaxKind.StringLiteral) { return undefined; } const nameText = moduleExportNameTextEscaped(name); const suppressInteropError = nameText === InternalSymbolName.Default && allowSyntheticDefaultImports; const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); if (targetSymbol) { // Note: The empty string is a valid module export name: // // import { "" as foo } from "./foo"; // export { foo as "" }; // if (nameText || name.kind === SyntaxKind.StringLiteral) { if (isShorthandAmbientModuleSymbol(moduleSymbol)) { return moduleSymbol; } let symbolFromVariable: Symbol | undefined; // First check if module was specified with "export=". If so, get the member from the resolved type if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) { symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), nameText, /*skipObjectFunctionPropertyAugment*/ true); } else { symbolFromVariable = getPropertyOfVariable(targetSymbol, nameText); } // if symbolFromVariable is export - get its final target symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); let symbolFromModule = getExportOfModule(targetSymbol, nameText, specifier, dontResolveAlias); if (symbolFromModule === undefined && nameText === InternalSymbolName.Default) { const file = moduleSymbol.declarations?.find(isSourceFile); if (isOnlyImportableAsDefault(moduleSpecifier, moduleSymbol) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) { symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } } const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ? combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : symbolFromModule || symbolFromVariable; if (isImportOrExportSpecifier(specifier) && isOnlyImportableAsDefault(moduleSpecifier, moduleSymbol) && nameText !== InternalSymbolName.Default) { error(name, Diagnostics.Named_imports_from_a_JSON_file_into_an_ECMAScript_module_are_not_allowed_when_module_is_set_to_0, ModuleKind[moduleKind]); } else if (!symbol) { errorNoModuleMemberSymbol(moduleSymbol, targetSymbol, node, name); } return symbol; } } } function errorNoModuleMemberSymbol(moduleSymbol: Symbol, targetSymbol: Symbol, node: Node, name: ModuleExportName) { const moduleName = getFullyQualifiedName(moduleSymbol, node); const declarationName = declarationNameToString(name); const suggestion = isIdentifier(name) ? getSuggestedSymbolForNonexistentModule(name, targetSymbol) : undefined; if (suggestion !== undefined) { const suggestionName = symbolToString(suggestion); const diagnostic = error(name, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, moduleName, declarationName, suggestionName); if (suggestion.valueDeclaration) { addRelatedInfo(diagnostic, createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName)); } } else { if (moduleSymbol.exports?.has(InternalSymbolName.Default)) { error( name, Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead, moduleName, declarationName, ); } else { reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName); } } } function reportNonExportedMember(node: Node, name: ModuleExportName, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { const localSymbol = tryCast(moduleSymbol.valueDeclaration, canHaveLocals)?.locals?.get(moduleExportNameTextEscaped(name)); const exports = moduleSymbol.exports; if (localSymbol) { const exportedEqualsSymbol = exports?.get(InternalSymbolName.ExportEquals); if (exportedEqualsSymbol) { getSymbolIfSameReference(exportedEqualsSymbol, localSymbol) ? reportInvalidImportEqualsExportMember(node, name, declarationName, moduleName) : error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); } else { const exportedSymbol = exports ? find(symbolsToArray(exports), symbol => !!getSymbolIfSameReference(symbol, localSymbol)) : undefined; const diagnostic = exportedSymbol ? error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_exported_as_2, moduleName, declarationName, symbolToString(exportedSymbol)) : error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_not_exported, moduleName, declarationName); if (localSymbol.declarations) { addRelatedInfo(diagnostic, ...map(localSymbol.declarations, (decl, index) => createDiagnosticForNode(decl, index === 0 ? Diagnostics._0_is_declared_here : Diagnostics.and_here, declarationName))); } } } else { error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); } } function reportInvalidImportEqualsExportMember(node: Node, name: ModuleExportName, declarationName: string, moduleName: string) { if (moduleKind >= ModuleKind.ES2015) { const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_default_import : Diagnostics._0_can_only_be_imported_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; error(name, message, declarationName); } else { if (isInJSFile(node)) { const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_using_a_default_import : Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; error(name, message, declarationName); } else { const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_a_default_import : Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; error(name, message, declarationName, declarationName, moduleName); } } } function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined { if (isImportSpecifier(node) && moduleExportNameIsDefault(node.propertyName || node.name)) { const specifier = getModuleSpecifierForImportOrExport(node); const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); if (moduleSymbol) { return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias); } } const root = isBindingElement(node) ? getRootDeclaration(node) as VariableDeclaration : node.parent.parent.parent; const commonJSPropertyAccess = getCommonJSPropertyAccess(root); const resolved = getExternalModuleMember(root, commonJSPropertyAccess || node, dontResolveAlias); const name = node.propertyName || node.name; if (commonJSPropertyAccess && resolved && isIdentifier(name)) { return resolveSymbol(getPropertyOfType(getTypeOfSymbol(resolved), name.escapedText), dontResolveAlias); } markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); return resolved; } function getCommonJSPropertyAccess(node: Node) { if (isVariableDeclaration(node) && node.initializer && isPropertyAccessExpression(node.initializer)) { return node.initializer; } } function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol | undefined { if (canHaveSymbol(node.parent)) { const resolved = resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); return resolved; } } function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { const name = node.propertyName || node.name; if (moduleExportNameIsDefault(name)) { const specifier = getModuleSpecifierForImportOrExport(node); const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); if (moduleSymbol) { return getTargetofModuleDefault(moduleSymbol, node, !!dontResolveAlias); } } const resolved = node.parent.parent.moduleSpecifier ? getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : name.kind === SyntaxKind.StringLiteral ? undefined : // Skip for invalid syntax like this: export { "x" } resolveEntityName(name, meaning, /*ignoreErrors*/ false, dontResolveAlias); markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); return resolved; } function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { const expression = isExportAssignment(node) ? node.expression : node.right; const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias); markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); return resolved; } function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) { if (isClassExpression(expression)) { return checkExpressionCached(expression).symbol; } if (!isEntityName(expression) && !isEntityNameExpression(expression)) { return undefined; } const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias); if (aliasLike) { return aliasLike; } checkExpressionCached(expression); return getNodeLinks(expression).resolvedSymbol; } function getTargetOfAccessExpression(node: AccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined { if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { return undefined; } return getTargetOfAliasLikeExpression(node.parent.right, dontRecursivelyResolve); } function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve = false): Symbol | undefined { switch (node.kind) { case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.VariableDeclaration: return getTargetOfImportEqualsDeclaration(node as ImportEqualsDeclaration | VariableDeclaration, dontRecursivelyResolve); case SyntaxKind.ImportClause: return getTargetOfImportClause(node as ImportClause, dontRecursivelyResolve); case SyntaxKind.NamespaceImport: return getTargetOfNamespaceImport(node as NamespaceImport, dontRecursivelyResolve); case SyntaxKind.NamespaceExport: return getTargetOfNamespaceExport(node as NamespaceExport, dontRecursivelyResolve); case SyntaxKind.ImportSpecifier: case SyntaxKind.BindingElement: return getTargetOfImportSpecifier(node as ImportSpecifier | BindingElement, dontRecursivelyResolve); case SyntaxKind.ExportSpecifier: return getTargetOfExportSpecifier(node as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve); case SyntaxKind.ExportAssignment: case SyntaxKind.BinaryExpression: return getTargetOfExportAssignment(node as ExportAssignment | BinaryExpression, dontRecursivelyResolve); case SyntaxKind.NamespaceExportDeclaration: return getTargetOfNamespaceExportDeclaration(node as NamespaceExportDeclaration, dontRecursivelyResolve); case SyntaxKind.ShorthandPropertyAssignment: return resolveEntityName((node as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontRecursivelyResolve); case SyntaxKind.PropertyAssignment: return getTargetOfAliasLikeExpression((node as PropertyAssignment).initializer, dontRecursivelyResolve); case SyntaxKind.ElementAccessExpression: case SyntaxKind.PropertyAccessExpression: return getTargetOfAccessExpression(node as AccessExpression, dontRecursivelyResolve); default: return Debug.fail(); } } /** * Indicates that a symbol is an alias that does not merge with a local declaration. * OR Is a JSContainer which may merge an alias with a local declaration */ function isNonLocalAlias(symbol: Symbol | undefined, excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace): symbol is Symbol { if (!symbol) return false; return (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias || !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment); } function resolveSymbol(symbol: Symbol, dontResolveAlias?: boolean): Symbol; function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined { return !dontResolveAlias && isNonLocalAlias(symbol) ? resolveAlias(symbol) : symbol; } function resolveAlias(symbol: Symbol): Symbol { Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); const links = getSymbolLinks(symbol); if (!links.aliasTarget) { links.aliasTarget = resolvingSymbol; const node = getDeclarationOfAliasSymbol(symbol); if (!node) return Debug.fail(); const target = getTargetOfAliasDeclaration(node); if (links.aliasTarget === resolvingSymbol) { links.aliasTarget = target || unknownSymbol; } else { error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); } } else if (links.aliasTarget === resolvingSymbol) { links.aliasTarget = unknownSymbol; } return links.aliasTarget; } function tryResolveAlias(symbol: Symbol): Symbol | undefined { const links = getSymbolLinks(symbol); if (links.aliasTarget !== resolvingSymbol) { return resolveAlias(symbol); } return undefined; } /** * Gets combined flags of a `symbol` and all alias targets it resolves to. `resolveAlias` * is typically recursive over chains of aliases, but stops mid-chain if an alias is merged * with another exported symbol, e.g. * ```ts * // a.ts * export const a = 0; * // b.ts * export { a } from "./a"; * export type a = number; * // c.ts * import { a } from "./b"; * ``` * Calling `resolveAlias` on the `a` in c.ts would stop at the merged symbol exported * from b.ts, even though there is still more alias to resolve. Consequently, if we were * trying to determine if the `a` in c.ts has a value meaning, looking at the flags on * the local symbol and on the symbol returned by `resolveAlias` is not enough. * @returns SymbolFlags.All if `symbol` is an alias that ultimately resolves to `unknown`; * combined flags of all alias targets otherwise. */ function getSymbolFlags(symbol: Symbol, excludeTypeOnlyMeanings?: boolean, excludeLocalMeanings?: boolean): SymbolFlags { const typeOnlyDeclaration = excludeTypeOnlyMeanings && getTypeOnlyAliasDeclaration(symbol); const typeOnlyDeclarationIsExportStar = typeOnlyDeclaration && isExportDeclaration(typeOnlyDeclaration); const typeOnlyResolution = typeOnlyDeclaration && ( typeOnlyDeclarationIsExportStar ? resolveExternalModuleName(typeOnlyDeclaration.moduleSpecifier, typeOnlyDeclaration.moduleSpecifier, /*ignoreErrors*/ true) : resolveAlias(typeOnlyDeclaration.symbol) ); const typeOnlyExportStarTargets = typeOnlyDeclarationIsExportStar && typeOnlyResolution ? getExportsOfModule(typeOnlyResolution) : undefined; let flags = excludeLocalMeanings ? SymbolFlags.None : symbol.flags; let seenSymbols; while (symbol.flags & SymbolFlags.Alias) { const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); if ( !typeOnlyDeclarationIsExportStar && target === typeOnlyResolution || typeOnlyExportStarTargets?.get(target.escapedName) === target ) { break; } if (target === unknownSymbol) { return SymbolFlags.All; } // Optimizations - try to avoid creating or adding to // `seenSymbols` if possible if (target === symbol || seenSymbols?.has(target)) { break; } if (target.flags & SymbolFlags.Alias) { if (seenSymbols) { seenSymbols.add(target); } else { seenSymbols = new Set([symbol, target]); } } flags |= target.flags; symbol = target; } return flags; } /** * Marks a symbol as type-only if its declaration is syntactically type-only. * If it is not itself marked type-only, but resolves to a type-only alias * somewhere in its resolution chain, save a reference to the type-only alias declaration * so the alias _not_ marked type-only can be identified as _transitively_ type-only. * * This function is called on each alias declaration that could be type-only or resolve to * another type-only alias during `resolveAlias`, so that later, when an alias is used in a * JS-emitting expression, we can quickly determine if that symbol is effectively type-only * and issue an error if so. * * @param aliasDeclaration The alias declaration not marked as type-only * @param immediateTarget The symbol to which the alias declaration immediately resolves * @param finalTarget The symbol to which the alias declaration ultimately resolves * @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration` * has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified * names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the * import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b` * must still be checked for a type-only marker, overwriting the previous negative result if found. */ function markSymbolOfAliasDeclarationIfTypeOnly( aliasDeclaration: Declaration | undefined, immediateTarget: Symbol | undefined, finalTarget: Symbol | undefined, overwriteEmpty: boolean, exportStarDeclaration?: ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }, exportStarName?: __String, ): boolean { if (!aliasDeclaration || isPropertyAccessExpression(aliasDeclaration)) return false; // If the declaration itself is type-only, mark it and return. // No need to check what it resolves to. const sourceSymbol = getSymbolOfDeclaration(aliasDeclaration); if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { const links = getSymbolLinks(sourceSymbol); links.typeOnlyDeclaration = aliasDeclaration; return true; } if (exportStarDeclaration) { const links = getSymbolLinks(sourceSymbol); links.typeOnlyDeclaration = exportStarDeclaration; if (sourceSymbol.escapedName !== exportStarName) { links.typeOnlyExportStarName = exportStarName; } return true; } const links = getSymbolLinks(sourceSymbol); return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty) || markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, finalTarget, overwriteEmpty); } function markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationLinks: SymbolLinks, target: Symbol | undefined, overwriteEmpty: boolean): boolean { if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) { const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target; const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration); aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false; } return !!aliasDeclarationLinks.typeOnlyDeclaration; } /** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */ function getTypeOnlyAliasDeclaration(symbol: Symbol, include?: SymbolFlags): TypeOnlyAliasDeclaration | undefined { if (!(symbol.flags & SymbolFlags.Alias)) { return undefined; } const links = getSymbolLinks(symbol); if (links.typeOnlyDeclaration === undefined) { // We need to set a WIP value here to prevent reentrancy during `getImmediateAliasedSymbol` which, paradoxically, can depend on this links.typeOnlyDeclaration = false; const resolved = resolveSymbol(symbol); // do this before the `resolveImmediate` below, as it uses a different circularity cache and we might hide a circularity error if we blindly get the immediate alias first // While usually the alias will have been marked during the pass by the full typecheck, we may still need to calculate the alias declaration now markSymbolOfAliasDeclarationIfTypeOnly(symbol.declarations?.[0], getDeclarationOfAliasSymbol(symbol) && getImmediateAliasedSymbol(symbol), resolved, /*overwriteEmpty*/ true); } if (include === undefined) { return links.typeOnlyDeclaration || undefined; } if (links.typeOnlyDeclaration) { const resolved = links.typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration ? resolveSymbol(getExportsOfModule(links.typeOnlyDeclaration.symbol.parent!).get(links.typeOnlyExportStarName || symbol.escapedName))! : resolveAlias(links.typeOnlyDeclaration.symbol); return getSymbolFlags(resolved) & include ? links.typeOnlyDeclaration : undefined; } return undefined; } // This function is only for imports with entity names function getSymbolOfPartOfRightHandSideOfImportEquals(entityName: EntityName, dontResolveAlias?: boolean): Symbol | undefined { // There are three things we might try to look for. In the following examples, // the search term is enclosed in |...|: // // import a = |b|; // Namespace // import a = |b.c|; // Value, type, namespace // import a = |b.c|.d; // Namespace if (entityName.kind === SyntaxKind.Identifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { entityName = entityName.parent as QualifiedName; } // Check for case 1 and 3 in the above example if (entityName.kind === SyntaxKind.Identifier || entityName.parent.kind === SyntaxKind.QualifiedName) { return resolveEntityName(entityName, SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); } else { // Case 2 in above example // entityName.kind could be a QualifiedName or a Missing identifier Debug.assert(entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration); return resolveEntityName(entityName, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); } } function getFullyQualifiedName(symbol: Symbol, containingLocation?: Node): string { return symbol.parent ? getFullyQualifiedName(symbol.parent, containingLocation) + "." + symbolToString(symbol) : symbolToString(symbol, containingLocation, /*meaning*/ undefined, SymbolFormatFlags.DoNotIncludeSymbolChain | SymbolFormatFlags.AllowAnyNodeKind); } function getContainingQualifiedNameNode(node: QualifiedName) { while (isQualifiedName(node.parent)) { node = node.parent; } return node; } function tryGetQualifiedNameAsValue(node: QualifiedName) { let left: Identifier | QualifiedName = getFirstIdentifier(node); let symbol = resolveName(left, left, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); if (!symbol) { return undefined; } while (isQualifiedName(left.parent)) { const type = getTypeOfSymbol(symbol); symbol = getPropertyOfType(type, left.parent.right.escapedText); if (!symbol) { return undefined; } left = left.parent; } return symbol; } /** * Resolves a qualified name and any involved aliases. */ function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined { if (nodeIsMissing(name)) { return undefined; } const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(name) ? meaning & SymbolFlags.Value : 0); let symbol: Symbol | undefined; if (name.kind === SyntaxKind.Identifier) { const message = meaning === namespaceMeaning || nodeIsSynthesized(name) ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name)); const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined; symbol = getMergedSymbol(resolveName(location || name, name, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, /*isUse*/ true, /*excludeGlobals*/ false)); if (!symbol) { return getMergedSymbol(symbolFromJSPrototype); } } else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { const left = name.kind === SyntaxKind.QualifiedName ? name.left : name.expression; const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name; let namespace = resolveEntityName(left, namespaceMeaning, ignoreErrors, /*dontResolveAlias*/ false, location); if (!namespace || nodeIsMissing(right)) { return undefined; } else if (namespace === unknownSymbol) { return namespace; } if ( namespace.valueDeclaration && isInJSFile(namespace.valueDeclaration) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler && isVariableDeclaration(namespace.valueDeclaration) && namespace.valueDeclaration.initializer && isCommonJsRequire(namespace.valueDeclaration.initializer) ) { const moduleName = (namespace.valueDeclaration.initializer as CallExpression).arguments[0] as StringLiteral; const moduleSym = resolveExternalModuleName(moduleName, moduleName); if (moduleSym) { const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); if (resolvedModuleSymbol) { namespace = resolvedModuleSymbol; } } } symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning)); if (!symbol && (namespace.flags & SymbolFlags.Alias)) { // `namespace` can be resolved further if there was a symbol merge with a re-export symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(resolveAlias(namespace)), right.escapedText, meaning)); } if (!symbol) { if (!ignoreErrors) { const namespaceName = getFullyQualifiedName(namespace); const declarationName = declarationNameToString(right); const suggestionForNonexistentModule = getSuggestedSymbolForNonexistentModule(right, namespace); if (suggestionForNonexistentModule) { error(right, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, namespaceName, declarationName, symbolToString(suggestionForNonexistentModule)); return undefined; } const containingQualifiedName = isQualifiedName(name) && getContainingQualifiedNameNode(name); const canSuggestTypeof = globalObjectType // !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); if (typeAlias) { return; } const host = getJSDocHost(node); if (host && isExpressionStatement(host) && isPrototypePropertyAssignment(host.expression)) { // /** @param {K} p */ X.prototype.m = function () { } isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l) || isJSDocImportTag(l)); // An override clause will take effect for type-only imports and import types, and allows importing the types across formats, regardless of // normal mode restrictions if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext && !hasResolutionModeOverride(overrideHost)) { if (findAncestor(location, isImportEqualsDeclaration)) { // ImportEquals in a ESM file resolving to another ESM file error(errorNode, Diagnostics.Module_0_cannot_be_imported_using_this_construct_The_specifier_only_resolves_to_an_ES_module_which_cannot_be_imported_with_require_Use_an_ECMAScript_import_instead, moduleReference); } else { // CJS file resolving to an ESM file let diagnosticDetails; const ext = tryGetExtensionFromPath(currentSourceFile.fileName); if (ext === Extension.Ts || ext === Extension.Js || ext === Extension.Tsx || ext === Extension.Jsx) { diagnosticDetails = createModeMismatchDetails(currentSourceFile); } const message = overrideHost?.kind === SyntaxKind.ImportDeclaration && overrideHost.importClause?.isTypeOnly ? Diagnostics.Type_only_import_of_an_ECMAScript_module_from_a_CommonJS_module_must_have_a_resolution_mode_attribute : overrideHost?.kind === SyntaxKind.ImportType ? Diagnostics.Type_import_of_an_ECMAScript_module_from_a_CommonJS_module_must_have_a_resolution_mode_attribute : Diagnostics.The_current_file_is_a_CommonJS_module_whose_imports_will_produce_require_calls_however_the_referenced_file_is_an_ECMAScript_module_and_cannot_be_imported_with_require_Consider_writing_a_dynamic_import_0_call_instead; diagnostics.add(createDiagnosticForNodeFromMessageChain( getSourceFileOfNode(errorNode), errorNode, chainDiagnosticMessages(diagnosticDetails, message, moduleReference), )); } } } // merged symbol is module declaration symbol combined with all augmentations return getMergedSymbol(sourceFile.symbol); } if (errorNode && moduleNotFoundError && !isSideEffectImport(errorNode)) { // report errors only if it was requested error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName); } return undefined; } if (patternAmbientModules) { const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference); if (pattern) { // If the module reference matched a pattern ambient module ('*.foo') but there's also a // module augmentation by the specific name requested ('a.foo'), we store the merged symbol // by the augmentation name ('a.foo'), because asking for *.foo should not give you exports // from a.foo. const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference); if (augmentation) { return getMergedSymbol(augmentation); } return getMergedSymbol(pattern.symbol); } } if (!errorNode) { return undefined; } // May be an untyped module. If so, ignore resolutionDiagnostic. if (resolvedModule && !resolutionExtensionIsTSOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) { if (isForAugmentation) { const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented; error(errorNode, diag, moduleReference, resolvedModule!.resolvedFileName); } else { errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, currentSourceFile, mode, resolvedModule!, moduleReference); } // Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first. return undefined; } if (moduleNotFoundError) { // See if this was possibly a projectReference redirect if (resolvedModule) { const redirect = host.getRedirectFromSourceFile(resolvedModule.resolvedFileName); if (redirect?.outputDts) { error(errorNode, Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect.outputDts, resolvedModule.resolvedFileName); return undefined; } } if (resolutionDiagnostic) { error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); } else { const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference); const resolutionIsNode16OrNext = moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext; if ( !getResolveJsonModule(compilerOptions) && fileExtensionIs(moduleReference, Extension.Json) && moduleResolutionKind !== ModuleResolutionKind.Classic && hasJsonModuleEmitEnabled(compilerOptions) ) { error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); } else if (mode === ModuleKind.ESNext && resolutionIsNode16OrNext && isExtensionlessRelativePathImport) { const absoluteRef = getNormalizedAbsolutePath(moduleReference, getDirectoryPath(currentSourceFile.path)); const suggestedExt = suggestedExtensions.find(([actualExt, _importExt]) => host.fileExists(absoluteRef + actualExt))?.[1]; if (suggestedExt) { error(errorNode, Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_node16_or_nodenext_Did_you_mean_0, moduleReference + suggestedExt); } else { error(errorNode, Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_ECMAScript_imports_when_moduleResolution_is_node16_or_nodenext_Consider_adding_an_extension_to_the_import_path); } } else { if (host.getResolvedModule(currentSourceFile, moduleReference, mode)?.alternateResult) { const errorInfo = createModuleNotFoundChain(currentSourceFile, host, moduleReference, mode, moduleReference); errorOrSuggestion(/*isError*/ true, errorNode, chainDiagnosticMessages(errorInfo, moduleNotFoundError, moduleReference)); } else { error(errorNode, moduleNotFoundError, moduleReference); } } } } return undefined; function getSuggestedImportSource(tsExtension: string) { const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension); /** * Direct users to import source with .js extension if outputting an ES module. * @see https://github.com/microsoft/TypeScript/issues/42151 */ if (emitModuleKindIsNonNodeESM(moduleKind) || mode === ModuleKind.ESNext) { const preferTs = isDeclarationFileName(moduleReference) && shouldAllowImportingTsExtension(compilerOptions); const ext = tsExtension === Extension.Mts || tsExtension === Extension.Dmts ? preferTs ? ".mts" : ".mjs" : tsExtension === Extension.Cts || tsExtension === Extension.Dmts ? preferTs ? ".cts" : ".cjs" : preferTs ? ".ts" : ".js"; return importSourceWithoutExtension + ext; } return importSourceWithoutExtension; } } function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, sourceFile: SourceFile, mode: ResolutionMode, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { if (isSideEffectImport(errorNode)) { return; } let errorInfo: DiagnosticMessageChain | undefined; if (!isExternalModuleNameRelative(moduleReference) && packageId) { errorInfo = createModuleNotFoundChain(sourceFile, host, moduleReference, mode, packageId.name); } errorOrSuggestion( isError, errorNode, chainDiagnosticMessages( errorInfo, Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, moduleReference, resolvedFileName, ), ); } function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined { if (moduleSymbol?.exports) { const exportEquals = resolveSymbol(moduleSymbol.exports.get(InternalSymbolName.ExportEquals), dontResolveAlias); const exported = getCommonJsExportEquals(getMergedSymbol(exportEquals), getMergedSymbol(moduleSymbol)); return getMergedSymbol(exported) || moduleSymbol; } return undefined; } function getCommonJsExportEquals(exported: Symbol | undefined, moduleSymbol: Symbol): Symbol | undefined { if (!exported || exported === unknownSymbol || exported === moduleSymbol || moduleSymbol.exports!.size === 1 || exported.flags & SymbolFlags.Alias) { return exported; } const links = getSymbolLinks(exported); if (links.cjsExportMerged) { return links.cjsExportMerged; } const merged = exported.flags & SymbolFlags.Transient ? exported : cloneSymbol(exported); merged.flags = merged.flags | SymbolFlags.ValueModule; if (merged.exports === undefined) { merged.exports = createSymbolTable(); } moduleSymbol.exports!.forEach((s, name) => { if (name === InternalSymbolName.ExportEquals) return; merged.exports!.set(name, merged.exports!.has(name) ? mergeSymbol(merged.exports!.get(name)!, s) : s); }); if (merged === exported) { // We just mutated a symbol, reset any cached links we may have already set // (Notably required to make late bound members appear) getSymbolLinks(merged).resolvedExports = undefined; getSymbolLinks(merged).resolvedMembers = undefined; } getSymbolLinks(merged).cjsExportMerged = merged; return links.cjsExportMerged = merged; } // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' // references a symbol that is at least declared as a module or a variable. The target of the 'export =' may // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). function resolveESModuleSymbol(moduleSymbol: Symbol | undefined, referencingLocation: Node, dontResolveAlias: boolean, suppressInteropError: boolean): Symbol | undefined { const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); if (!dontResolveAlias && symbol) { if (!suppressInteropError && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) && !getDeclarationOfKind(symbol, SyntaxKind.SourceFile)) { const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; error(referencingLocation, Diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName); return symbol; } const referenceParent = referencingLocation.parent; const namespaceImport = isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent); if (namespaceImport || isImportCall(referenceParent)) { const reference = isImportCall(referenceParent) ? referenceParent.arguments[0] : referenceParent.moduleSpecifier; const type = getTypeOfSymbol(symbol); const defaultOnlyType = getTypeWithSyntheticDefaultOnly(type, symbol, moduleSymbol!, reference); if (defaultOnlyType) { return cloneTypeAsModuleType(symbol, defaultOnlyType, referenceParent); } const targetFile = moduleSymbol?.declarations?.find(isSourceFile); const usageMode = getEmitSyntaxForModuleSpecifierExpression(reference); let exportModuleDotExportsSymbol: Symbol | undefined; if ( namespaceImport && targetFile && ModuleKind.Node20 void): void { const exports = getExportsOfModule(moduleSymbol); exports.forEach((symbol, key) => { if (!isReservedMemberName(key)) { cb(symbol, key); } }); const exportEquals = resolveExternalModuleSymbol(moduleSymbol); if (exportEquals !== moduleSymbol) { const type = getTypeOfSymbol(exportEquals); if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { forEachPropertyOfType(type, (symbol, escapedName) => { cb(symbol, escapedName); }); } } } function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { const symbolTable = getExportsOfModule(moduleSymbol); if (symbolTable) { return symbolTable.get(memberName); } } function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); if (symbol) { return symbol; } const exportEquals = resolveExternalModuleSymbol(moduleSymbol); if (exportEquals === moduleSymbol) { return undefined; } const type = getTypeOfSymbol(exportEquals); return shouldTreatPropertiesOfExternalModuleAsExports(type) ? getPropertyOfType(type, memberName) : undefined; } function shouldTreatPropertiesOfExternalModuleAsExports(resolvedExternalModuleType: Type) { return !(resolvedExternalModuleType.flags & TypeFlags.Primitive || getObjectFlags(resolvedExternalModuleType) & ObjectFlags.Class || // `isArrayOrTupleLikeType` is too expensive to use in this auto-imports hot path isArrayType(resolvedExternalModuleType) || isTupleType(resolvedExternalModuleType)); } function getExportsOfSymbol(symbol: Symbol): SymbolTable { return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) : symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) : symbol.exports || emptySymbols; } function getExportsOfModule(moduleSymbol: Symbol): SymbolTable { const links = getSymbolLinks(moduleSymbol); if (!links.resolvedExports) { const { exports, typeOnlyExportStarMap } = getExportsOfModuleWorker(moduleSymbol); links.resolvedExports = exports; links.typeOnlyExportStarMap = typeOnlyExportStarMap; } return links.resolvedExports; } interface ExportCollisionTracker { specifierText: string; exportsWithDuplicate?: ExportDeclaration[]; } type ExportCollisionTrackerTable = Map<__string exportcollisiontracker>; /** * Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument * Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables */ function extendExportSymbols(target: SymbolTable, source: SymbolTable | undefined, lookupTable?: ExportCollisionTrackerTable, exportNode?: ExportDeclaration) { if (!source) return; source.forEach((sourceSymbol, id) => { if (id === InternalSymbolName.Default) return; const targetSymbol = target.get(id); if (!targetSymbol) { target.set(id, sourceSymbol); if (lookupTable && exportNode) { lookupTable.set(id, { specifierText: getTextOfNode(exportNode.moduleSpecifier!), }); } } else if (lookupTable && exportNode && targetSymbol && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) { const collisionTracker = lookupTable.get(id)!; if (!collisionTracker.exportsWithDuplicate) { collisionTracker.exportsWithDuplicate = [exportNode]; } else { collisionTracker.exportsWithDuplicate.push(exportNode); } } }); } function getExportsOfModuleWorker(moduleSymbol: Symbol) { const visitedSymbols: Symbol[] = []; let typeOnlyExportStarMap: Map<__string exportdeclaration readonly istypeonly: true modulespecifier: expression> | undefined; const nonTypeOnlyNames = new Set<__string>(); // A module defined by an 'export=' consists of one export that needs to be resolved moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); const exports = visit(moduleSymbol) || emptySymbols; if (typeOnlyExportStarMap) { nonTypeOnlyNames.forEach(name => typeOnlyExportStarMap!.delete(name)); } return { exports, typeOnlyExportStarMap, }; // The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example, // module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error. function visit(symbol: Symbol | undefined, exportStar?: ExportDeclaration, isTypeOnly?: boolean): SymbolTable | undefined { if (!isTypeOnly && symbol?.exports) { // Add non-type-only names before checking if we've visited this module, // because we might have visited it via an 'export type *', and visiting // again with 'export *' will override the type-onlyness of its exports. symbol.exports.forEach((_, name) => nonTypeOnlyNames.add(name)); } if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { return; } const symbols = new Map(symbol.exports); // All export * declarations are collected in an __export symbol by the binder const exportStars = symbol.exports.get(InternalSymbolName.ExportStar); if (exportStars) { const nestedSymbols = createSymbolTable(); const lookupTable: ExportCollisionTrackerTable = new Map(); if (exportStars.declarations) { for (const node of exportStars.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); const exportedSymbols = visit(resolvedModule, node as ExportDeclaration, isTypeOnly || (node as ExportDeclaration).isTypeOnly); extendExportSymbols( nestedSymbols, exportedSymbols, lookupTable, node as ExportDeclaration, ); } } lookupTable.forEach(({ exportsWithDuplicate }, id) => { // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.has(id)) { return; } for (const node of exportsWithDuplicate) { diagnostics.add(createDiagnosticForNode( node, Diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, lookupTable.get(id)!.specifierText, unescapeLeadingUnderscores(id), )); } }); extendExportSymbols(symbols, nestedSymbols); } if (exportStar?.isTypeOnly) { typeOnlyExportStarMap ??= new Map(); symbols.forEach((_, escapedName) => typeOnlyExportStarMap!.set( escapedName, exportStar as ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }, ) ); } return symbols; } } function getMergedSymbol(symbol: Symbol): Symbol; function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined; function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined { let merged: Symbol; return symbol && symbol.mergeId && (merged = mergedSymbols[symbol.mergeId]) ? merged : symbol; } function getSymbolOfDeclaration(node: Declaration): Symbol { return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); } /** * Get the merged symbol for a node. If you know the node is a `Declaration`, it is faster and more type safe to * use use `getSymbolOfDeclaration` instead. */ function getSymbolOfNode(node: Node): Symbol | undefined { return canHaveSymbol(node) ? getSymbolOfDeclaration(node) : undefined; } function getParentOfSymbol(symbol: Symbol): Symbol | undefined { return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); } function getFunctionExpressionParentSymbolOrSymbol(symbol: Symbol) { return symbol.valueDeclaration?.kind === SyntaxKind.ArrowFunction || symbol.valueDeclaration?.kind === SyntaxKind.FunctionExpression ? getSymbolOfNode(symbol.valueDeclaration.parent) || symbol : symbol; } function getAlternativeContainingModules(symbol: Symbol, enclosingDeclaration: Node): Symbol[] { const containingFile = getSourceFileOfNode(enclosingDeclaration); const id = getNodeId(containingFile); const links = getSymbolLinks(symbol); let results: Symbol[] | undefined; if (links.extendedContainersByFile && (results = links.extendedContainersByFile.get(id))) { return results; } if (containingFile && containingFile.imports) { // Try to make an import using an import already in the enclosing file, if possible for (const importRef of containingFile.imports) { if (nodeIsSynthesized(importRef)) continue; // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error const resolvedModule = resolveExternalModuleName(enclosingDeclaration, importRef, /*ignoreErrors*/ true); if (!resolvedModule) continue; const ref = getAliasForSymbolInContainer(resolvedModule, symbol); if (!ref) continue; results = append(results, resolvedModule); } if (length(results)) { (links.extendedContainersByFile || (links.extendedContainersByFile = new Map())).set(id, results!); return results!; } } if (links.extendedContainers) { return links.extendedContainers; } // No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached) const otherFiles = host.getSourceFiles(); for (const file of otherFiles) { if (!isExternalModule(file)) continue; const sym = getSymbolOfDeclaration(file); const ref = getAliasForSymbolInContainer(sym, symbol); if (!ref) continue; results = append(results, sym); } return links.extendedContainers = results || emptyArray; } /** * Attempts to find the symbol corresponding to the container a symbol is in - usually this * is just its' `.parent`, but for locals, this value is `undefined` */ function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): Symbol[] | undefined { const container = getParentOfSymbol(symbol); // Type parameters end up in the `members` lists but are not externally visible if (container && !(symbol.flags & SymbolFlags.TypeParameter)) { return getWithAlternativeContainers(container); } const candidates = mapDefined(symbol.declarations, d => { if (!isAmbientModule(d) && d.parent) { // direct children of a module if (hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { return getSymbolOfDeclaration(d.parent as Declaration); } // export ='d member of an ambient module if (isModuleBlock(d.parent) && d.parent.parent && resolveExternalModuleSymbol(getSymbolOfDeclaration(d.parent.parent)) === symbol) { return getSymbolOfDeclaration(d.parent.parent); } } if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) { if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) { return getSymbolOfDeclaration(getSourceFileOfNode(d)); } checkExpressionCached(d.parent.left.expression); return getNodeLinks(d.parent.left.expression).resolvedSymbol; } }); if (!length(candidates)) { return undefined; } const containers = mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); let bestContainers: Symbol[] = []; let alternativeContainers: Symbol[] = []; for (const container of containers) { const [bestMatch, ...rest] = getWithAlternativeContainers(container); bestContainers = append(bestContainers, bestMatch); alternativeContainers = addRange(alternativeContainers, rest); } return concatenate(bestContainers, alternativeContainers); function getWithAlternativeContainers(container: Symbol) { const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer); const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration); const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning); if ( enclosingDeclaration && container.flags & getQualifiedLeftMeaning(meaning) && getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*useOnlyExternalAliasing*/ false) ) { return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope } // we potentially have a symbol which is a member of the instance side of something - look for a variable in scope with the container's type // which may be acting like a namespace (eg, `Symbol` acts like a namespace when looking up `Symbol.toStringTag`) const firstVariableMatch = !(container.flags & getQualifiedLeftMeaning(meaning)) && container.flags & SymbolFlags.Type && getDeclaredTypeOfSymbol(container).flags & TypeFlags.Object && meaning === SymbolFlags.Value ? forEachSymbolTableInScope(enclosingDeclaration, t => { return forEachEntry(t, s => { if (s.flags & getQualifiedLeftMeaning(meaning) && getTypeOfSymbol(s) === getDeclaredTypeOfSymbol(container)) { return s; } }); }) : undefined; let res = firstVariableMatch ? [firstVariableMatch, ...additionalContainers, container] : [...additionalContainers, container]; res = append(res, objectLiteralContainer); res = addRange(res, reexportContainers); return res; } function fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { return container && getFileSymbolIfFileSymbolExportEqualsContainer(d, container); } } function getVariableDeclarationOfObjectLiteral(symbol: Symbol, meaning: SymbolFlags) { // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations!); if (meaning & SymbolFlags.Value && firstDecl && firstDecl.parent && isVariableDeclaration(firstDecl.parent)) { if (isObjectLiteralExpression(firstDecl) && firstDecl === firstDecl.parent.initializer || isTypeLiteralNode(firstDecl) && firstDecl === firstDecl.parent.type) { return getSymbolOfDeclaration(firstDecl.parent); } } } function getFileSymbolIfFileSymbolExportEqualsContainer(d: Declaration, container: Symbol) { const fileSymbol = getExternalModuleContainer(d); const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals); return exported && getSymbolIfSameReference(exported, container) ? fileSymbol : undefined; } function getAliasForSymbolInContainer(container: Symbol, symbol: Symbol) { if (container === getParentOfSymbol(symbol)) { // fast path, `symbol` is either already the alias or isn't aliased return symbol; } // Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return // the container itself as the alias for the symbol const exportEquals = container.exports && container.exports.get(InternalSymbolName.ExportEquals); if (exportEquals && getSymbolIfSameReference(exportEquals, symbol)) { return container; } const exports = getExportsOfSymbol(container); const quick = exports.get(symbol.escapedName); if (quick && getSymbolIfSameReference(quick, symbol)) { return quick; } return forEachEntry(exports, exported => { if (getSymbolIfSameReference(exported, symbol)) { return exported; } }); } /** * Checks if two symbols, through aliasing and/or merging, refer to the same thing */ function getSymbolIfSameReference(s1: Symbol, s2: Symbol) { if (getMergedSymbol(resolveSymbol(getMergedSymbol(s1))) === getMergedSymbol(resolveSymbol(getMergedSymbol(s2)))) { return s1; } } function getExportSymbolOfValueSymbolIfExported(symbol: Symbol): Symbol; function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined; function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined { return getMergedSymbol(symbol && (symbol.flags & SymbolFlags.ExportValue) !== 0 && symbol.exportSymbol || symbol); } function symbolIsValue(symbol: Symbol, includeTypeOnlyMembers?: boolean): boolean { return !!( symbol.flags & SymbolFlags.Value || symbol.flags & SymbolFlags.Alias && getSymbolFlags(symbol, !includeTypeOnlyMembers) & SymbolFlags.Value ); } function createType(flags: TypeFlags): Type { const result = new Type(checker, flags); typeCount++; result.id = typeCount; tracing?.recordType(result); return result; } function createTypeWithSymbol(flags: TypeFlags, symbol: Symbol): Type { const result = createType(flags); result.symbol = symbol; return result; } function createOriginType(flags: TypeFlags): Type { return new Type(checker, flags); } function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags = ObjectFlags.None, debugIntrinsicName?: string): IntrinsicType { checkIntrinsicName(intrinsicName, debugIntrinsicName); const type = createType(kind) as IntrinsicType; type.intrinsicName = intrinsicName; type.debugIntrinsicName = debugIntrinsicName; type.objectFlags = objectFlags | ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.IsGenericTypeComputed | ObjectFlags.IsUnknownLikeUnionComputed | ObjectFlags.IsNeverIntersectionComputed; return type; } function checkIntrinsicName(name: string, debug: string | undefined) { const key = `${name},${debug ?? ""}`; if (seenIntrinsicNames.has(key)) { Debug.fail(`Duplicate intrinsic type name ${name}${debug ? ` (${debug})` : ""}; you may need to pass a name to createIntrinsicType.`); } seenIntrinsicNames.add(key); } function createObjectType(objectFlags: ObjectFlags, symbol?: Symbol): ObjectType { const type = createTypeWithSymbol(TypeFlags.Object, symbol!) as ObjectType; type.objectFlags = objectFlags; type.members = undefined; type.properties = undefined; type.callSignatures = undefined; type.constructSignatures = undefined; type.indexInfos = undefined; return type; } function createTypeofType() { return getUnionType(arrayFrom(typeofNEFacts.keys(), getStringLiteralType)); } function createTypeParameter(symbol?: Symbol) { return createTypeWithSymbol(TypeFlags.TypeParameter, symbol!) as TypeParameter; } // A reserved member name starts with two underscores, but the third character cannot be an underscore, // @, or #. A third underscore indicates an escaped form of an identifier that started // with at least two underscores. The @ character indicates that the name is denoted by a well known ES // Symbol instance and the # character indicates that the name is a PrivateIdentifier. function isReservedMemberName(name: __String) { return (name as string).charCodeAt(0) === CharacterCodes._ && (name as string).charCodeAt(1) === CharacterCodes._ && (name as string).charCodeAt(2) !== CharacterCodes._ && (name as string).charCodeAt(2) !== CharacterCodes.at && (name as string).charCodeAt(2) !== CharacterCodes.hash; } function getNamedMembers(members: SymbolTable): Symbol[] { let result: Symbol[] | undefined; members.forEach((symbol, id) => { if (isNamedMember(symbol, id)) { (result || (result = [])).push(symbol); } }); return result || emptyArray; } function isNamedMember(member: Symbol, escapedName: __String) { return !isReservedMemberName(escapedName) && symbolIsValue(member); } function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] { const result = getNamedMembers(members); const index = getIndexSymbolFromSymbolTable(members); return index ? concatenate(result, [index]) : result; } function setStructuredTypeMembers(type: StructuredType, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { const resolved = type as ResolvedType; resolved.members = members; resolved.properties = emptyArray; resolved.callSignatures = callSignatures; resolved.constructSignatures = constructSignatures; resolved.indexInfos = indexInfos; // This can loop back to getPropertyOfType() which would crash if `callSignatures` & `constructSignatures` are not initialized. if (members !== emptySymbols) resolved.properties = getNamedMembers(members); return resolved; } function createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { return setStructuredTypeMembers(createObjectType(ObjectFlags.Anonymous, symbol), members, callSignatures, constructSignatures, indexInfos); } function getResolvedTypeWithoutAbstractConstructSignatures(type: ResolvedType) { if (type.constructSignatures.length === 0) return type; if (type.objectTypeWithoutAbstractConstructSignatures) return type.objectTypeWithoutAbstractConstructSignatures; const constructSignatures = filter(type.constructSignatures, signature => !(signature.flags & SignatureFlags.Abstract)); if (type.constructSignatures === constructSignatures) return type; const typeCopy = createAnonymousType( type.symbol, type.members, type.callSignatures, some(constructSignatures) ? constructSignatures : emptyArray, type.indexInfos, ); type.objectTypeWithoutAbstractConstructSignatures = typeCopy; typeCopy.objectTypeWithoutAbstractConstructSignatures = typeCopy; return typeCopy; } function forEachSymbolTableInScope(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean, scopeNode?: Node) => T): T { let result: T; for (let location = enclosingDeclaration; location; location = location.parent) { // Locals of a source file are not in scope (because they get merged into the global symbol table) if (canHaveLocals(location) && location.locals && !isGlobalSourceFile(location)) { if (result = callback(location.locals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { return result; } } switch (location.kind) { case SyntaxKind.SourceFile: if (!isExternalOrCommonJsModule(location as SourceFile)) { break; } // falls through case SyntaxKind.ModuleDeclaration: const sym = getSymbolOfDeclaration(location as ModuleDeclaration); // `sym` may not have exports if this module declaration is backed by the symbol for a `const` that's being rewritten // into a namespace - in such cases, it's best to just let the namespace appear empty (the const members couldn't have referred // to one another anyway) if (result = callback(sym?.exports || emptySymbols, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { return result; } break; case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.InterfaceDeclaration: // Type parameters are bound into `members` lists so they can merge across declarations // This is troublesome, since in all other respects, they behave like locals :cries: // TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol // lookup logic in terms of `resolveName` would be nice // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would // trigger resolving late-bound names, which we may already be in the process of doing while we're here! let table: Map<__string symbol> | undefined; // TODO: Should this filtered table be cached in some way? (getSymbolOfDeclaration(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols).forEach((memberSymbol, key) => { if (memberSymbol.flags & (SymbolFlags.Type & ~SymbolFlags.Assignment)) { (table || (table = createSymbolTable())).set(key, memberSymbol); } }); if (table && (result = callback(table, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ false, location))) { return result; } break; } } return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); } function getQualifiedLeftMeaning(rightMeaning: SymbolFlags) { // If we are looking in value space, the parent meaning is value, other wise it is namespace return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace; } function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap = new Map()): Symbol[] | undefined { if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) { return undefined; } const links = getSymbolLinks(symbol); const cache = (links.accessibleChainCache ||= new Map()); // Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more const firstRelevantLocation = forEachSymbolTableInScope(enclosingDeclaration, (_, __, ___, node) => node); const key = `${useOnlyExternalAliasing ? 0 : 1}|${firstRelevantLocation ? getNodeId(firstRelevantLocation) : 0}|${meaning}`; if (cache.has(key)) { return cache.get(key); } const id = getSymbolId(symbol); let visitedSymbolTables = visitedSymbolTablesMap.get(id); if (!visitedSymbolTables) { visitedSymbolTablesMap.set(id, visitedSymbolTables = []); } const result = forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable); cache.set(key, result); return result; /** * @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already) */ function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean): Symbol[] | undefined { if (!pushIfUnique(visitedSymbolTables!, symbols)) { return undefined; } const result = trySymbolTable(symbols, ignoreQualification, isLocalNameLookup); visitedSymbolTables!.pop(); return result; } function canQualifySymbol(symbolFromSymbolTable: Symbol, meaning: SymbolFlags) { // If the symbol is equivalent and doesn't need further qualification, this symbol is accessible return !needsQualification(symbolFromSymbolTable, enclosingDeclaration, meaning) || // If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too !!getAccessibleSymbolChain(symbolFromSymbolTable.parent, enclosingDeclaration, getQualifiedLeftMeaning(meaning), useOnlyExternalAliasing, visitedSymbolTablesMap); } function isAccessible(symbolFromSymbolTable: Symbol, resolvedAliasSymbol?: Symbol, ignoreQualification?: boolean) { return (symbol === (resolvedAliasSymbol || symbolFromSymbolTable) || getMergedSymbol(symbol) === getMergedSymbol(resolvedAliasSymbol || symbolFromSymbolTable)) && // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) // and if symbolFromSymbolTable or alias resolution matches the symbol, // check the symbol can be qualified, it is only then this symbol is accessible !some(symbolFromSymbolTable.declarations, hasNonGlobalAugmentationExternalModuleSymbol) && (ignoreQualification || canQualifySymbol(getMergedSymbol(symbolFromSymbolTable), meaning)); } function trySymbolTable(symbols: SymbolTable, ignoreQualification: boolean | undefined, isLocalNameLookup: boolean | undefined): Symbol[] | undefined { // If symbol is directly available by its name in the symbol table if (isAccessible(symbols.get(symbol!.escapedName)!, /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { return [symbol!]; } // Check if symbol is any of the aliases in scope const result = forEachEntry(symbols, symbolFromSymbolTable => { if ( symbolFromSymbolTable.flags & SymbolFlags.Alias && symbolFromSymbolTable.escapedName !== InternalSymbolName.ExportEquals && symbolFromSymbolTable.escapedName !== InternalSymbolName.Default && !(isUMDExportSymbol(symbolFromSymbolTable) && enclosingDeclaration && isExternalModule(getSourceFileOfNode(enclosingDeclaration))) // If `!useOnlyExternalAliasing`, we can use any type of alias to get the name && (!useOnlyExternalAliasing || some(symbolFromSymbolTable.declarations, isExternalModuleImportEqualsDeclaration)) // If we're looking up a local name to reference directly, omit namespace reexports, otherwise when we're trawling through an export list to make a dotted name, we can keep it && (isLocalNameLookup ? !some(symbolFromSymbolTable.declarations, isNamespaceReexportDeclaration) : true) // While exports are generally considered to be in scope, export-specifier declared symbols are _not_ // See similar comment in `resolveName` for details && (ignoreQualification || !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) ) { const resolvedImportedSymbol = resolveAlias(symbolFromSymbolTable); const candidate = getCandidateListForSymbol(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification); if (candidate) { return candidate; } } if (symbolFromSymbolTable.escapedName === symbol!.escapedName && symbolFromSymbolTable.exportSymbol) { if (isAccessible(getMergedSymbol(symbolFromSymbolTable.exportSymbol), /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { return [symbol!]; } } }); // If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined); } function getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { return [symbolFromSymbolTable]; } // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain // but only if the symbolFromSymbolTable can be qualified const candidateTable = getExportsOfSymbol(resolvedImportedSymbol); const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable, /*ignoreQualification*/ true); if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) { return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports); } } } function needsQualification(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags) { let qualify = false; forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { // If symbol of this name is not available in the symbol table we are ok let symbolFromSymbolTable = getMergedSymbol(symbolTable.get(symbol.escapedName)); if (!symbolFromSymbolTable) { // Continue to the next symbol table return false; } // If the symbol with this name is present it should refer to the symbol if (symbolFromSymbolTable === symbol) { // No need to qualify return true; } // Qualify if the symbol from symbol table has same meaning as expected const shouldResolveAlias = symbolFromSymbolTable.flags & SymbolFlags.Alias && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier); symbolFromSymbolTable = shouldResolveAlias ? resolveAlias(symbolFromSymbolTable) : symbolFromSymbolTable; const flags = shouldResolveAlias ? getSymbolFlags(symbolFromSymbolTable) : symbolFromSymbolTable.flags; if (flags & meaning) { qualify = true; return true; } // Continue to the next symbol table return false; }); return qualify; } function isPropertyOrMethodDeclarationSymbol(symbol: Symbol) { if (symbol.declarations && symbol.declarations.length) { for (const declaration of symbol.declarations) { switch (declaration.kind) { case SyntaxKind.PropertyDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: continue; default: return false; } } return true; } return false; } function isTypeSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); return access.accessibility === SymbolAccessibility.Accessible; } function isValueSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); return access.accessibility === SymbolAccessibility.Accessible; } function isSymbolAccessibleByFlags(typeSymbol: Symbol, enclosingDeclaration: Node | undefined, flags: SymbolFlags): boolean { const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, flags, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ false); return access.accessibility === SymbolAccessibility.Accessible; } function isAnySymbolAccessible(symbols: Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult | undefined { if (!length(symbols)) return; let hadAccessibleChain: Symbol | undefined; let earlyModuleBail = false; for (const symbol of symbols!) { // Symbol is accessible if it by itself is accessible const accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/ false); if (accessibleSymbolChain) { hadAccessibleChain = symbol; const hasAccessibleDeclarations = hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible); if (hasAccessibleDeclarations) { return hasAccessibleDeclarations; } } if (allowModules) { if (some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { if (shouldComputeAliasesToMakeVisible) { earlyModuleBail = true; // Generally speaking, we want to use the aliases that already exist to refer to a module, if present // In order to do so, we need to find those aliases in order to retain them in declaration emit; so // if we are in declaration emit, we cannot use the fast path for module visibility until we've exhausted // all other visibility options (in order to capture the possible aliases used to reference the module) continue; } // Any meaning of a module symbol is always accessible via an `import` type return { accessibility: SymbolAccessibility.Accessible, }; } } // If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible. // It could be a qualified symbol and hence verify the path // e.g.: // module m { // export class c { // } // } // const x: typeof m.c // In the above example when we start with checking if typeof m.c symbol is accessible, // we are going to see if c can be accessed in scope directly. // But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible // It is accessible if the parent m is accessible because then m.c can be accessed through qualification const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning); const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); if (parentResult) { return parentResult; } } if (earlyModuleBail) { return { accessibility: SymbolAccessibility.Accessible, }; } if (hadAccessibleChain) { return { accessibility: SymbolAccessibility.NotAccessible, errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, }; } } /** * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested * * @param symbol a Symbol to check if accessible * @param enclosingDeclaration a Node containing reference to the symbol * @param meaning a SymbolFlags to check if such meaning of the symbol is accessible * @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible */ function isSymbolAccessible(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult { return isSymbolAccessibleWorker(symbol, enclosingDeclaration, meaning, shouldComputeAliasesToMakeVisible, /*allowModules*/ true); } function isSymbolAccessibleWorker(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult { if (symbol && enclosingDeclaration) { const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible, allowModules); if (result) { return result; } // This could be a symbol that is not exported in the external module // or it could be a symbol from different external module that is not aliased and hence cannot be named const symbolExternalModule = forEach(symbol.declarations, getExternalModuleContainer); if (symbolExternalModule) { const enclosingExternalModule = getExternalModuleContainer(enclosingDeclaration); if (symbolExternalModule !== enclosingExternalModule) { // name from different external module that is not visible return { accessibility: SymbolAccessibility.CannotBeNamed, errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), errorModuleName: symbolToString(symbolExternalModule), errorNode: isInJSFile(enclosingDeclaration) ? enclosingDeclaration : undefined, }; } } // Just a local name that is not accessible return { accessibility: SymbolAccessibility.NotAccessible, errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), }; } return { accessibility: SymbolAccessibility.Accessible }; } function getExternalModuleContainer(declaration: Node) { const node = findAncestor(declaration, hasExternalModuleSymbol); return node && getSymbolOfDeclaration(node as AmbientModuleDeclaration | SourceFile); } function hasExternalModuleSymbol(declaration: Node) { return isAmbientModule(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); } function hasNonGlobalAugmentationExternalModuleSymbol(declaration: Node) { return isModuleWithStringLiteralName(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); } function hasVisibleDeclarations(symbol: Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined { let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined; if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) { return undefined; } return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible }; function getIsDeclarationVisible(declaration: Declaration) { if (!isDeclarationVisible(declaration)) { // Mark the unexported alias as visible if its parent is visible // because these kind of aliases can be used to name types in declaration file const anyImportSyntax = getAnyImportSyntax(declaration); if ( anyImportSyntax && !hasSyntacticModifier(anyImportSyntax, ModifierFlags.Export) && // import clause without export isDeclarationVisible(anyImportSyntax.parent) ) { return addVisibleAlias(declaration, anyImportSyntax); } else if ( isVariableDeclaration(declaration) && isVariableStatement(declaration.parent.parent) && !hasSyntacticModifier(declaration.parent.parent, ModifierFlags.Export) && // unexported variable statement isDeclarationVisible(declaration.parent.parent.parent) ) { return addVisibleAlias(declaration, declaration.parent.parent); } else if ( isLateVisibilityPaintedStatement(declaration) // unexported top-level statement && !hasSyntacticModifier(declaration, ModifierFlags.Export) && isDeclarationVisible(declaration.parent) ) { return addVisibleAlias(declaration, declaration); } else if (isBindingElement(declaration)) { if ( symbol.flags & SymbolFlags.Alias && isInJSFile(declaration) && declaration.parent?.parent // exported import-like top-level JS require statement && isVariableDeclaration(declaration.parent.parent) && declaration.parent.parent.parent?.parent && isVariableStatement(declaration.parent.parent.parent.parent) && !hasSyntacticModifier(declaration.parent.parent.parent.parent, ModifierFlags.Export) && declaration.parent.parent.parent.parent.parent // check if the thing containing the variable statement is visible (ie, the file) && isDeclarationVisible(declaration.parent.parent.parent.parent.parent) ) { return addVisibleAlias(declaration, declaration.parent.parent.parent.parent); } else if (symbol.flags & SymbolFlags.BlockScopedVariable) { const rootDeclaration = walkUpBindingElementsAndPatterns(declaration); if (rootDeclaration.kind === SyntaxKind.Parameter) { return false; } const variableStatement = rootDeclaration.parent.parent; if (variableStatement.kind !== SyntaxKind.VariableStatement) { return false; } if (hasSyntacticModifier(variableStatement, ModifierFlags.Export)) { return true; } if (!isDeclarationVisible(variableStatement.parent)) { return false; } return addVisibleAlias(declaration, variableStatement); } } // Declaration is not visible return false; } return true; } function addVisibleAlias(declaration: Declaration, aliasingStatement: LateVisibilityPaintedStatement) { // In function "buildTypeDisplay" where we decide whether to write type-alias or serialize types, // we want to just check if type- alias is accessible or not but we don't care about emitting those alias at that time // since we will do the emitting later in trackSymbol. if (shouldComputeAliasToMakeVisible) { getNodeLinks(declaration).isVisible = true; aliasesToMakeVisible = appendIfUnique(aliasesToMakeVisible, aliasingStatement); } return true; } } function getMeaningOfEntityNameReference(entityName: EntityNameOrEntityNameExpression): SymbolFlags { // get symbol of the first identifier of the entityName let meaning: SymbolFlags; if ( entityName.parent.kind === SyntaxKind.TypeQuery || entityName.parent.kind === SyntaxKind.ExpressionWithTypeArguments && !isPartOfTypeNode(entityName.parent) || entityName.parent.kind === SyntaxKind.ComputedPropertyName || entityName.parent.kind === SyntaxKind.TypePredicate && (entityName.parent as TypePredicateNode).parameterName === entityName ) { // Typeof value meaning = SymbolFlags.Value | SymbolFlags.ExportValue; } else if ( entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration || (entityName.parent.kind === SyntaxKind.QualifiedName && (entityName.parent as QualifiedName).left === entityName) || (entityName.parent.kind === SyntaxKind.PropertyAccessExpression && (entityName.parent as PropertyAccessExpression).expression === entityName) || (entityName.parent.kind === SyntaxKind.ElementAccessExpression && (entityName.parent as ElementAccessExpression).expression === entityName) ) { // Left identifier from type reference or TypeAlias // Entity name of the import declaration meaning = SymbolFlags.Namespace; } else { // Type Reference or TypeAlias entity = Identifier meaning = SymbolFlags.Type; } return meaning; } function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node, shouldComputeAliasToMakeVisible = true): SymbolVisibilityResult { const meaning = getMeaningOfEntityNameReference(entityName); const firstIdentifier = getFirstIdentifier(entityName); const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) { return { accessibility: SymbolAccessibility.Accessible }; } if (!symbol && isThisIdentifier(firstIdentifier) && isSymbolAccessible(getSymbolOfDeclaration(getThisContainer(firstIdentifier, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)), firstIdentifier, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { return { accessibility: SymbolAccessibility.Accessible }; } if (!symbol) { return { accessibility: SymbolAccessibility.NotResolved, errorSymbolName: getTextOfNode(firstIdentifier), errorNode: firstIdentifier, }; } // Verify if the symbol is accessible return hasVisibleDeclarations(symbol, shouldComputeAliasToMakeVisible) || { accessibility: SymbolAccessibility.NotAccessible, errorSymbolName: getTextOfNode(firstIdentifier), errorNode: firstIdentifier, }; } function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string { let nodeFlags = NodeBuilderFlags.IgnoreErrors; let internalNodeFlags = InternalNodeBuilderFlags.None; if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) { nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing; } if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { nodeFlags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName; } if (flags & SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope) { nodeFlags |= NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; } if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) { internalNodeFlags |= InternalNodeBuilderFlags.DoNotIncludeSymbolChain; } if (flags & SymbolFormatFlags.WriteComputedProps) { internalNodeFlags |= InternalNodeBuilderFlags.WriteComputedProps; } const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToNode : nodeBuilder.symbolToEntityName; return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); function symbolToStringWorker(writer: EmitTextWriter) { const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags, internalNodeFlags)!; // TODO: GH#18217 // add neverAsciiEscape for GH#39027 const printer = enclosingDeclaration?.kind === SyntaxKind.SourceFile ? createPrinterWithRemoveCommentsNeverAsciiEscape() : createPrinterWithRemoveComments(); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); return writer; } } function signatureToString( signature: Signature, enclosingDeclaration?: Node, flags = TypeFormatFlags.None, kind?: SignatureKind, writer?: EmitTextWriter, maximumLength?: number, verbosityLevel?: number, out?: WriterContextOut, ): string { return writer ? signatureToStringWorker(writer).getText() : usingSingleLineStringWriter(signatureToStringWorker); function signatureToStringWorker(writer: EmitTextWriter) { let sigOutput: SyntaxKind; if (flags & TypeFormatFlags.WriteArrowStyleSignature) { sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; } else { sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructSignature : SyntaxKind.CallSignature; } const sig = nodeBuilder.signatureToSignatureDeclaration( signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName, /*internalFlags*/ undefined, /*tracker*/ undefined, maximumLength, verbosityLevel, out, ); const printer = createPrinterWithRemoveCommentsOmitTrailingSemicolon(); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 return writer; } } function typeToString( type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter(""), maximumLength?: number, verbosityLevel?: number, out?: WriterContextOut, ): string { const noTruncation = !maximumLength && compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; const typeNode = nodeBuilder.typeToTypeNode( type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), /*internalFlags*/ undefined, /*tracker*/ undefined, maximumLength, verbosityLevel, out, ); if (typeNode === undefined) return Debug.fail("should always get typenode"); // The unresolved type gets a synthesized comment on `any` to hint to users that it's not a plain `any`. // Otherwise, we always strip comments out. const printer = type !== unresolvedType ? createPrinterWithRemoveComments() : createPrinterWithDefaults(); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); const result = writer.getText(); const maxLength = maximumLength || (noTruncation ? noTruncationMaximumTruncationLength * 2 : defaultMaximumTruncationLength * 2); if (maxLength && result && result.length >= maxLength) { return result.substr(0, maxLength - "...".length) + "..."; } return result; } function getTypeNamesForErrorDisplay(left: Type, right: Type): [string, string] { let leftStr = symbolValueDeclarationIsContextSensitive(left.symbol) ? typeToString(left, left.symbol.valueDeclaration) : typeToString(left); let rightStr = symbolValueDeclarationIsContextSensitive(right.symbol) ? typeToString(right, right.symbol.valueDeclaration) : typeToString(right); if (leftStr === rightStr) { leftStr = getTypeNameForErrorDisplay(left); rightStr = getTypeNameForErrorDisplay(right); } return [leftStr, rightStr]; } function getTypeNameForErrorDisplay(type: Type) { return typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType); } function symbolValueDeclarationIsContextSensitive(symbol: Symbol): boolean { return symbol && !!symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration); } function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags { return flags & TypeFormatFlags.NodeBuilderFlagsMask; } function isClassInstanceSide(type: Type) { return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || (!!(type.flags & TypeFlags.Object) && !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone))); } /** * Same as getTypeFromTypeNode, but for use in createNodeBuilder * Inside createNodeBuilder we shadow getTypeFromTypeNode to make sure anyone using this function will call the local version that does type mapping if appropriate * This function is used to still be able to call the original getTypeFromTypeNode from the local scope version of getTypeFromTypeNode */ function getTypeFromTypeNodeWithoutContext(node: TypeNode) { return getTypeFromTypeNode(node); } function createNodeBuilder() { const syntacticBuilderResolver: SyntacticTypeNodeBuilderResolver = { evaluateEntityNameExpression, isExpandoFunctionDeclaration, hasLateBindableName, shouldRemoveDeclaration(context: SyntacticTypeNodeBuilderContext, node: DynamicNamedDeclaration) { return !((context as NodeBuilderContext).internalFlags & InternalNodeBuilderFlags.AllowUnresolvedNames && isEntityNameExpression(node.name.expression) && checkComputedPropertyName(node.name).flags & TypeFlags.Any); }, createRecoveryBoundary(context) { return createRecoveryBoundary(context as NodeBuilderContext); }, isDefinitelyReferenceToGlobalSymbolObject, getAllAccessorDeclarations: getAllAccessorDeclarationsForDeclaration, requiresAddingImplicitUndefined(declaration: ParameterDeclaration | JSDocParameterTag | PropertyDeclaration | PropertySignature | JSDocPropertyTag, symbol: Symbol | undefined, enclosingDeclaration: Node | undefined): boolean { switch (declaration.kind) { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.JSDocPropertyTag: symbol ??= getSymbolOfDeclaration(declaration); const type = getTypeOfSymbol(symbol); return !!(symbol.flags & SymbolFlags.Property && symbol.flags & SymbolFlags.Optional && isOptionalDeclaration(declaration) && (symbol as MappedSymbol).links?.mappedType && containsNonMissingUndefinedType(type)); case SyntaxKind.Parameter: case SyntaxKind.JSDocParameterTag: return requiresAddingImplicitUndefined(declaration, enclosingDeclaration); default: Debug.assertNever(declaration); } }, isOptionalParameter, isUndefinedIdentifierExpression(node: Identifier) { return getSymbolAtLocation(node) === undefinedSymbol; }, isEntityNameVisible(context, entityName, shouldComputeAliasToMakeVisible) { return isEntityNameVisible(entityName, context.enclosingDeclaration!, shouldComputeAliasToMakeVisible); }, serializeExistingTypeNode(context, typeNode, addUndefined) { return serializeExistingTypeNode(context as NodeBuilderContext, typeNode, !!addUndefined); }, serializeReturnTypeForSignature(syntacticContext, signatureDeclaration, symbol) { const context = syntacticContext as NodeBuilderContext; const signature = getSignatureFromDeclaration(signatureDeclaration); symbol ??= getSymbolOfDeclaration(signatureDeclaration); const returnType = context.enclosingSymbolTypes.get(getSymbolId(symbol)) ?? instantiateType(getReturnTypeOfSignature(signature), context.mapper); return serializeInferredReturnTypeForSignature(context, signature, returnType); }, serializeTypeOfExpression(syntacticContext, expr) { const context = syntacticContext as NodeBuilderContext; const type = instantiateType(getWidenedType(getRegularTypeOfExpression(expr)), context.mapper); return typeToTypeNodeHelper(type, context); }, serializeTypeOfDeclaration(syntacticContext, declaration, symbol) { // Get type of the symbol if this is the valid symbol otherwise get type at location const context = syntacticContext as NodeBuilderContext; symbol ??= getSymbolOfDeclaration(declaration); let type = context.enclosingSymbolTypes?.get(getSymbolId(symbol)); if (type === undefined) { type = symbol.flags & SymbolFlags.Accessor && declaration.kind === SyntaxKind.SetAccessor ? instantiateType(getWriteTypeOfSymbol(symbol), context.mapper) : symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) ? instantiateType(getWidenedLiteralType(getTypeOfSymbol(symbol)), context.mapper) : errorType; } const addUndefinedForParameter = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration, context.enclosingDeclaration); if (addUndefinedForParameter) { type = getOptionalType(type); } return serializeInferredTypeForDeclaration(symbol, context, type); }, serializeNameOfParameter(context, parameter) { return parameterToParameterDeclarationName(getSymbolOfDeclaration(parameter), parameter, context as NodeBuilderContext); }, serializeEntityName(syntacticContext, node) { const context = syntacticContext as NodeBuilderContext; const symbol = getSymbolAtLocation(node, /*ignoreErrors*/ true); if (!symbol) return undefined; if (!isValueSymbolAccessible(symbol, context.enclosingDeclaration)) return undefined; return symbolToExpression(symbol, context, SymbolFlags.Value | SymbolFlags.ExportValue); }, serializeTypeName(context, node, isTypeOf, typeArguments) { return serializeTypeName(context as NodeBuilderContext, node, isTypeOf, typeArguments); }, getJsDocPropertyOverride(syntacticContext, jsDocTypeLiteral, jsDocProperty) { const context = syntacticContext as NodeBuilderContext; const name = isIdentifier(jsDocProperty.name) ? jsDocProperty.name : jsDocProperty.name.right; const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(context, jsDocTypeLiteral), name.escapedText); const overrideTypeNode = typeViaParent && jsDocProperty.typeExpression && getTypeFromTypeNode(context, jsDocProperty.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; return overrideTypeNode; }, enterNewScope(context, node) { if (isFunctionLike(node) || isJSDocSignature(node)) { const signature = getSignatureFromDeclaration(node); return enterNewScope(context as NodeBuilderContext, node, signature.parameters, signature.typeParameters); } else { const typeParameters = isConditionalTypeNode(node) ? getInferTypeParameters(node) : [getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter))]; return enterNewScope(context as NodeBuilderContext, node, /*expandedParams*/ undefined, typeParameters); } }, markNodeReuse(context: SyntacticTypeNodeBuilderContext, range: T, location: Node | undefined) { return setTextRange(context as NodeBuilderContext, range, location); }, trackExistingEntityName(context: SyntacticTypeNodeBuilderContext, node: T) { return trackExistingEntityName(node, context as NodeBuilderContext); }, trackComputedName(context: SyntacticTypeNodeBuilderContext, accessExpression: EntityNameOrEntityNameExpression) { trackComputedName(accessExpression, context.enclosingDeclaration, context as NodeBuilderContext); }, getModuleSpecifierOverride(syntacticContext, parent, lit) { const context = syntacticContext as NodeBuilderContext; if (context.bundled || context.enclosingFile !== getSourceFileOfNode(lit)) { let name = lit.text; const originalName = name; const nodeSymbol = getNodeLinks(parent).resolvedSymbol; const meaning = parent.isTypeOf ? SymbolFlags.Value : SymbolFlags.Type; const parentSymbol = nodeSymbol && isSymbolAccessible(nodeSymbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible && lookupSymbolChain(nodeSymbol, context, meaning, /*yieldModuleSymbol*/ true)[0]; if (parentSymbol && isExternalModuleSymbol(parentSymbol)) { name = getSpecifierForModuleSymbol(parentSymbol, context); } else { const targetFile = getExternalModuleFileFromDeclaration(parent); if (targetFile) { name = getSpecifierForModuleSymbol(targetFile.symbol, context); } } if (name.includes("/node_modules/")) { context.encounteredError = true; if (context.tracker.reportLikelyUnsafeImportRequiredError) { context.tracker.reportLikelyUnsafeImportRequiredError(name); } } if (name !== originalName) { return name; } } }, canReuseTypeNode(context, typeNode) { return canReuseTypeNode(context as NodeBuilderContext, typeNode); }, canReuseTypeNodeAnnotation(syntacticContext: SyntacticTypeNodeBuilderContext, node: Declaration, existing: TypeNode, symbol: Symbol, requiresAddingUndefined?: boolean) { const context = syntacticContext as NodeBuilderContext; if (context.enclosingDeclaration === undefined) return false; symbol ??= getSymbolOfDeclaration(node); let type = context.enclosingSymbolTypes?.get(getSymbolId(symbol)); if (type === undefined) { if (symbol.flags & SymbolFlags.Accessor) { type = node.kind === SyntaxKind.SetAccessor ? getWriteTypeOfSymbol(symbol) : getTypeOfAccessors(symbol); } else if (isValueSignatureDeclaration(node)) { type = getReturnTypeOfSignature(getSignatureFromDeclaration(node)); } else { type = getTypeOfSymbol(symbol); } } let annotationType = getTypeFromTypeNodeWithoutContext(existing); if (isErrorType(annotationType)) { // allow "reusing" type nodes that resolve to error types // those can't truly be reused but it prevents cascading errors in isolatedDeclarations // for source with errors there is no guarantee to emit correct code anyway return true; } if (requiresAddingUndefined && annotationType) { annotationType = addOptionality(annotationType, !isParameter(node)); } return !!annotationType && typeNodeIsEquivalentToType(node, type, annotationType) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type); }, }; return { syntacticBuilderResolver, typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker, maximumLength?: number, verbosityLevel?: number, out?: WriterContextOut) => withContext(enclosingDeclaration, flags, internalFlags, tracker, maximumLength, verbosityLevel, context => typeToTypeNodeHelper(type, context), out), typePredicateToTypePredicateNode: (typePredicate: TypePredicate, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => typePredicateToTypePredicateNodeHelper(typePredicate, context)), serializeTypeForDeclaration: (declaration: HasInferredType, symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => syntacticNodeBuilder.serializeTypeOfDeclaration(declaration, symbol, context)), serializeReturnTypeForSignature: (signature: SignatureDeclaration, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => syntacticNodeBuilder.serializeReturnTypeForSignature(signature, getSymbolOfDeclaration(signature), context)), serializeTypeForExpression: (expr: Expression, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => syntacticNodeBuilder.serializeTypeOfExpression(expr, context)), indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, /*typeNode*/ undefined)), signatureToSignatureDeclaration: (signature: Signature, kind: SignatureDeclaration["kind"], enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker, maximumLength?: number, verbosityLevel?: number, out?: WriterContextOut) => withContext(enclosingDeclaration, flags, internalFlags, tracker, maximumLength, verbosityLevel, context => signatureToSignatureDeclarationHelper(signature, kind, context), out), symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)), symbolToExpression: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => symbolToExpression(symbol, context, meaning)), symbolToTypeParameterDeclarations: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => typeParametersToTypeParameterDeclarations(symbol, context)), symbolToParameterDeclaration: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => symbolToParameterDeclaration(symbol, context)), typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker, maximumLength?: number, verbosityLevel?: number, out?: WriterContextOut) => withContext(enclosingDeclaration, flags, internalFlags, tracker, maximumLength, verbosityLevel, context => typeParameterToDeclaration(parameter, context), out), symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => symbolTableToDeclarationStatements(symbolTable, context)), symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, internalFlags, tracker, /*maximumLength*/ undefined, /*verbosityLevel*/ undefined, context => symbolToNode(symbol, context, meaning)), symbolToDeclarations, }; function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: false): Type; function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes: true): Type | undefined; function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: boolean): Type | undefined { const type = getTypeFromTypeNodeWithoutContext(node); if (!context.mapper) return type; const mappedType = instantiateType(type, context.mapper); return noMappedTypes && mappedType !== type ? undefined : mappedType; } /** * Unlike the utilities `setTextRange`, this checks if the `location` we're trying to set on `range` is within the * same file as the active context. If not, the range is not applied. This prevents us from copying ranges across files, * which will confuse the node printer (as it assumes all node ranges are within the current file). * Additionally, if `range` _isn't synthetic_, or isn't in the current file, it will _copy_ it to _remove_ its' position * information. * * It also calls `setOriginalNode` to setup a `.original` pointer, since you basically *always* want these in the node builder. */ function setTextRange(context: NodeBuilderContext, range: T, location: Node | undefined): T { if (!nodeIsSynthesized(range) || !(range.flags & NodeFlags.Synthesized) || !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(getOriginalNode(range))) { range = factory.cloneNode(range); // if `range` is synthesized or originates in another file, copy it so it definitely has synthetic positions } if (range === location) return range; if (!location) { return range; } // Don't overwrite the original node if `range` has an `original` node that points either directly or indirectly to `location` let original = range.original; while (original && original !== location) { original = original.original; } if (!original) { setOriginalNode(range, location); } // only set positions if range comes from the same file since copying text across files isn't supported by the emitter if (context.enclosingFile && context.enclosingFile === getSourceFileOfNode(getOriginalNode(location))) { return setTextRangeWorker(range, location); } return range; } function symbolToNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { if (context.internalFlags & InternalNodeBuilderFlags.WriteComputedProps) { if (symbol.valueDeclaration) { const name = getNameOfDeclaration(symbol.valueDeclaration); if (name && isComputedPropertyName(name)) return name; } const nameType = getSymbolLinks(symbol).nameType; if (nameType && nameType.flags & (TypeFlags.EnumLiteral | TypeFlags.UniqueESSymbol)) { context.enclosingDeclaration = nameType.symbol.valueDeclaration; return factory.createComputedPropertyName(symbolToExpression(nameType.symbol, context, meaning)); } } return symbolToExpression(symbol, context, meaning); } function symbolToDeclarations(symbol: Symbol, meaning: SymbolFlags, flags: NodeBuilderFlags, maximumLength?: number, verbosityLevel?: number, out?: WriterContextOut): Declaration[] { const nodes = withContext( /*enclosingDeclaration*/ undefined, flags, /*internalFlags*/ undefined, /*tracker*/ undefined, maximumLength, verbosityLevel, context => symbolToDeclarationsWorker(symbol, context), out, ); return mapDefined(nodes, node => { switch (node.kind) { case SyntaxKind.ClassDeclaration: return simplifyClassDeclaration(node as ClassDeclaration, symbol); case SyntaxKind.EnumDeclaration: return simplifyModifiers(node as EnumDeclaration, isEnumDeclaration, symbol); case SyntaxKind.InterfaceDeclaration: return simplifyInterfaceDeclaration(node as InterfaceDeclaration, symbol, meaning); case SyntaxKind.ModuleDeclaration: return simplifyModifiers(node as ModuleDeclaration, isModuleDeclaration, symbol); default: return undefined; } }); } function simplifyClassDeclaration(classDecl: ClassDeclaration, symbol: Symbol): Declaration { const classDeclarations = filter(symbol.declarations, isClassLike); const originalClassDecl = classDeclarations && classDeclarations.length > 0 ? classDeclarations[0] : classDecl; const modifiers = getEffectiveModifierFlags(originalClassDecl) & ~(ModifierFlags.Export | ModifierFlags.Ambient); const isAnonymous = isClassExpression(originalClassDecl); if (isAnonymous) { classDecl = factory.updateClassDeclaration( classDecl, classDecl.modifiers, /*name*/ undefined, classDecl.typeParameters, classDecl.heritageClauses, classDecl.members, ); } return factory.replaceModifiers(classDecl, modifiers); } function simplifyModifiers(newDecl: Declaration & HasModifiers, isDeclKind: (d: Declaration) => boolean, symbol: Symbol): Declaration & HasModifiers { const decls = filter(symbol.declarations, isDeclKind); const declWithModifiers = decls && decls.length > 0 ? decls[0] : newDecl; const modifiers = getEffectiveModifierFlags(declWithModifiers) & ~(ModifierFlags.Export | ModifierFlags.Ambient); return factory.replaceModifiers(newDecl, modifiers); } function simplifyInterfaceDeclaration(interfaceDecl: InterfaceDeclaration, symbol: Symbol, meaning: SymbolFlags): Declaration | undefined { if (!(meaning & SymbolFlags.Interface)) { return undefined; } return simplifyModifiers(interfaceDecl, isInterfaceDeclaration, symbol); } function symbolToDeclarationsWorker(symbol: Symbol, context: NodeBuilderContext): Statement[] { // We're already expanding the type: set it in the stack to avoid expanding it again. const type = getDeclaredTypeOfSymbol(symbol); context.typeStack.push(type.id); context.typeStack.push(-1); const table = createSymbolTable([symbol]); const statements = symbolTableToDeclarationStatements(table, context); context.typeStack.pop(); context.typeStack.pop(); return statements; } function withContext( enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, internalFlags: InternalNodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, maximumLength: number | undefined, verbosityLevel: number | undefined, cb: (context: NodeBuilderContext) => T, out?: WriterContextOut, ): T | undefined { const moduleResolverHost = tracker?.trackSymbol ? tracker.moduleResolverHost : (internalFlags || InternalNodeBuilderFlags.None) & InternalNodeBuilderFlags.DoNotIncludeSymbolChain ? createBasicNodeBuilderModuleSpecifierResolutionHost(host) : undefined; flags = flags || NodeBuilderFlags.None; const maxTruncationLength = maximumLength || (flags & NodeBuilderFlags.NoTruncation ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); const context: NodeBuilderContext = { enclosingDeclaration, enclosingFile: enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration), flags, internalFlags: internalFlags || InternalNodeBuilderFlags.None, tracker: undefined!, maxTruncationLength, maxExpansionDepth: verbosityLevel ?? -1, encounteredError: false, suppressReportInferenceFallback: false, reportedDiagnostic: false, visitedTypes: undefined, symbolDepth: undefined, inferTypeParameters: undefined, approximateLength: 0, trackedSymbols: undefined, bundled: !!compilerOptions.outFile && !!enclosingDeclaration && isExternalOrCommonJsModule(getSourceFileOfNode(enclosingDeclaration)), truncating: false, usedSymbolNames: undefined, remappedSymbolNames: undefined, remappedSymbolReferences: undefined, reverseMappedStack: undefined, mustCreateTypeParameterSymbolList: true, typeParameterSymbolList: undefined, mustCreateTypeParametersNamesLookups: true, typeParameterNames: undefined, typeParameterNamesByText: undefined, typeParameterNamesByTextNextNameCount: undefined, enclosingSymbolTypes: new Map(), mapper: undefined, depth: 0, typeStack: [], out: { canIncreaseExpansionDepth: false, truncated: false, }, }; context.tracker = new SymbolTrackerImpl(context, tracker, moduleResolverHost); const resultingNode = cb(context); if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { context.tracker.reportTruncationError(); } if (out) { out.canIncreaseExpansionDepth = context.out.canIncreaseExpansionDepth; out.truncated = context.out.truncated; } return context.encounteredError ? undefined : resultingNode; } function addSymbolTypeToContext(context: NodeBuilderContext, symbol: Symbol, type: Type) { const id = getSymbolId(symbol); const oldType = context.enclosingSymbolTypes.get(id); context.enclosingSymbolTypes.set(id, type); return restore; function restore() { if (oldType) { context.enclosingSymbolTypes.set(id, oldType); } else { context.enclosingSymbolTypes.delete(id); } } } function saveRestoreFlags(context: NodeBuilderContext) { const flags = context.flags; const internalFlags = context.internalFlags; const depth = context.depth; return restore; function restore() { context.flags = flags; context.internalFlags = internalFlags; context.depth = depth; } } function checkTruncationLengthIfExpanding(context: NodeBuilderContext): boolean { return context.maxExpansionDepth >= 0 && checkTruncationLength(context); } function checkTruncationLength(context: NodeBuilderContext): boolean { if (context.truncating) return context.truncating; return context.truncating = context.approximateLength > context.maxTruncationLength; } /** * Returns true if it's possible the type or one of its components can be expanded. */ function canPossiblyExpandType(type: Type, context: NodeBuilderContext): boolean { for (let i = 0; i 0) { return type.flags & TypeFlags.Union ? factory.createUnionTypeNode(typeNodes) : factory.createIntersectionTypeNode(typeNodes); } else { if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { context.encounteredError = true; } return undefined!; // TODO: GH#18217 } } if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { Debug.assert(!!(type.flags & TypeFlags.Object)); // The type is an object literal type. return createAnonymousTypeNode(type as ObjectType); } if (type.flags & TypeFlags.Index) { const indexedType = (type as IndexType).type; context.approximateLength += 6; const indexTypeNode = typeToTypeNodeHelper(indexedType, context); return factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, indexTypeNode); } if (type.flags & TypeFlags.TemplateLiteral) { const texts = (type as TemplateLiteralType).texts; const types = (type as TemplateLiteralType).types; const templateHead = factory.createTemplateHead(texts[0]); const templateSpans = factory.createNodeArray( map(types, (t, i) => factory.createTemplateLiteralTypeSpan( typeToTypeNodeHelper(t, context), (i conditionalTypeToTypeNode(type as ConditionalType)); } if (type.flags & TypeFlags.Substitution) { const typeNode = typeToTypeNodeHelper((type as SubstitutionType).baseType, context); const noInferSymbol = isNoInferType(type) && getGlobalTypeSymbol("NoInfer" as __String, /*reportErrors*/ false); return noInferSymbol ? symbolToTypeNode(noInferSymbol, context, SymbolFlags.Type, [typeNode]) : typeNode; } return Debug.fail("Should be unreachable."); function conditionalTypeToTypeNode(type: ConditionalType) { const checkTypeNode = typeToTypeNodeHelper(type.checkType, context); context.approximateLength += 15; if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && type.root.isDistributive && !(type.checkType.flags & TypeFlags.TypeParameter)) { const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); const name = typeParameterToName(newParam, context); const newTypeVariable = factory.createTypeReferenceNode(name); context.approximateLength += 37; // 15 each for two added conditionals, 7 for an added infer type const newMapper = prependTypeMapping(type.root.checkType, newParam, type.mapper); const saveInferTypeParameters = context.inferTypeParameters; context.inferTypeParameters = type.root.inferTypeParameters; const extendsTypeNode = typeToTypeNodeHelper(instantiateType(type.root.extendsType, newMapper), context); context.inferTypeParameters = saveInferTypeParameters; const trueTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(context, type.root.node.trueType), newMapper)); const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(context, type.root.node.falseType), newMapper)); // outermost conditional makes `T` a type parameter, allowing the inner conditionals to be distributive // second conditional makes `T` have `T & checkType` substitution, so it is correctly usable as the checkType // inner conditional runs the check the user provided on the check type (distributively) and returns the result // checkType extends infer T ? T extends checkType ? T extends extendsType ? trueType : falseType : never : never; // this is potentially simplifiable to // checkType extends infer T ? T extends checkType & extendsType ? trueType : falseType : never; // but that may confuse users who read the output more. // On the other hand, // checkType extends infer T extends checkType ? T extends extendsType ? trueType : falseType : never; // may also work with `infer ... extends ...` in, but would produce declarations only compatible with the latest TS. return factory.createConditionalTypeNode( checkTypeNode, factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable.typeName) as Identifier)), factory.createConditionalTypeNode( factory.createTypeReferenceNode(factory.cloneNode(name)), typeToTypeNodeHelper(type.checkType, context), factory.createConditionalTypeNode(newTypeVariable, extendsTypeNode, trueTypeNode, falseTypeNode), factory.createKeywordTypeNode(SyntaxKind.NeverKeyword), ), factory.createKeywordTypeNode(SyntaxKind.NeverKeyword), ); } const saveInferTypeParameters = context.inferTypeParameters; context.inferTypeParameters = type.root.inferTypeParameters; const extendsTypeNode = typeToTypeNodeHelper(type.extendsType, context); context.inferTypeParameters = saveInferTypeParameters; const trueTypeNode = typeToTypeNodeOrCircularityElision(getTrueTypeFromConditionalType(type)); const falseTypeNode = typeToTypeNodeOrCircularityElision(getFalseTypeFromConditionalType(type)); return factory.createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); } function typeToTypeNodeOrCircularityElision(type: Type) { if (type.flags & TypeFlags.Union) { if (context.visitedTypes?.has(getTypeId(type))) { if (!(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { context.encounteredError = true; context.tracker?.reportCyclicStructureError?.(); } return createElidedInformationPlaceholder(context); } return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context)); } return typeToTypeNodeHelper(type, context); } function isMappedTypeHomomorphic(type: MappedType) { return !!getHomomorphicTypeVariable(type); } function isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type: MappedType) { return !!type.target && isMappedTypeHomomorphic(type.target as MappedType) && !isMappedTypeHomomorphic(type); } function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); const readonlyToken = type.declaration.readonlyToken ? factory.createToken(type.declaration.readonlyToken.kind) as ReadonlyKeyword | PlusToken | MinusToken : undefined; const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined; let appropriateConstraintTypeNode: TypeNode; let newTypeVariable: TypeReferenceNode | undefined; let templateType = getTemplateTypeFromMappedType(type); const typeParameter = getTypeParameterFromMappedType(type); // If the mapped type isn't `keyof` constraint-declared, _but_ still has modifiers preserved, and its naive instantiation won't preserve modifiers because its constraint isn't `keyof` constrained, we have work to do const needsModifierPreservingWrapper = !isMappedTypeWithKeyofConstraintDeclaration(type) && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.Unknown) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && !(getConstraintTypeFromMappedType(type).flags & TypeFlags.TypeParameter && getConstraintOfTypeParameter(getConstraintTypeFromMappedType(type))?.flags! & TypeFlags.Index); if (isMappedTypeWithKeyofConstraintDeclaration(type)) { // We have a { [P in keyof T]: X } // We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType` if (isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { const newConstraintParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); const name = typeParameterToName(newConstraintParam, context); const target = type.target as MappedType; newTypeVariable = factory.createTypeReferenceNode(name); templateType = instantiateType( getTemplateTypeFromMappedType(target), makeArrayTypeMapper([getTypeParameterFromMappedType(target), getModifiersTypeFromMappedType(target)], [typeParameter, newConstraintParam]), ); } appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, newTypeVariable || typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); } else if (needsModifierPreservingWrapper) { // So, step 1: new type variable const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); const name = typeParameterToName(newParam, context); newTypeVariable = factory.createTypeReferenceNode(name); // step 2: make that new type variable itself the constraint node, making the mapped type `{[K in T_1]: Template}` appropriateConstraintTypeNode = newTypeVariable; } else { appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context); } const typeParameterNode = typeParameterToDeclarationWithConstraint(typeParameter, context, appropriateConstraintTypeNode); // nameType and templateType nodes have to be in the new scope const cleanup = enterNewScope(context, type.declaration, /*expandedParams*/ undefined, [getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(type.declaration.typeParameter))]); const nameTypeNode = type.declaration.nameType ? typeToTypeNodeHelper(getNameTypeFromMappedType(type)!, context) : undefined; const templateTypeNode = typeToTypeNodeHelper(removeMissingType(templateType, !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context); cleanup(); const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined); context.approximateLength += 10; const result = setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); if (isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { // homomorphic mapped type with a non-homomorphic naive inlining // wrap it with a conditional like `SomeModifiersType extends infer U ? {..the mapped type...} : never` to ensure the resulting // type stays homomorphic const originalConstraint = instantiateType(getConstraintOfTypeParameter(getTypeFromTypeNode(context, (type.declaration.typeParameter.constraint! as TypeOperatorNode).type) as TypeParameter) || unknownType, type.mapper); return factory.createConditionalTypeNode( typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context), factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, originalConstraint.flags & TypeFlags.Unknown ? undefined : typeToTypeNodeHelper(originalConstraint, context))), result, factory.createKeywordTypeNode(SyntaxKind.NeverKeyword), ); } else if (needsModifierPreservingWrapper) { // and step 3: once the mapped type is reconstructed, create a `ConstraintType extends infer T_1 extends keyof ModifiersType ? {[K in T_1]: Template} : never` // subtly different from the `keyof` constraint case, by including the `keyof` constraint on the `infer` type parameter, it doesn't rely on the constraint type being itself // constrained to a `keyof` type to preserve its modifier-preserving behavior. This is all basically because we preserve modifiers for a wider set of mapped types than // just homomorphic ones. return factory.createConditionalTypeNode( typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context), factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)))), result, factory.createKeywordTypeNode(SyntaxKind.NeverKeyword), ); } return result; } function createAnonymousTypeNode(type: ObjectType, forceClassExpansion = false, forceExpansion = false): TypeNode { const typeId = type.id; const symbol = type.symbol; if (symbol) { const isInstantiationExpressionType = !!(getObjectFlags(type) & ObjectFlags.InstantiationExpressionType); if (isInstantiationExpressionType) { const instantiationExpressionType = type as InstantiationExpressionType; const existing = instantiationExpressionType.node; // instantiationExpressionType.node is unreliable for constituents of unions and intersections. // declare const Err: typeof ErrImpl & (() => T); // type ErrAlias = typeof Err; // declare const e: ErrAlias; // ErrAlias = typeof Err = typeof ErrImpl & (() => number) // The problem is each constituent of the intersection will be associated with typeof Err // And when extracting a type for typeof ErrImpl from typeof Err does not make sense. if (isTypeQueryNode(existing) && getTypeFromTypeNode(context, existing) === type) { const typeNode = syntacticNodeBuilder.tryReuseExistingTypeNode(context, existing); if (typeNode) { return typeNode; } } if (context.visitedTypes?.has(typeId)) { return createElidedInformationPlaceholder(context); } return visitAndTransformType(type, createTypeNodeFromObjectType); } const isInstanceType = isClassInstanceSide(type) ? SymbolFlags.Type : SymbolFlags.Value; if (isJSConstructor(symbol.valueDeclaration)) { // Instance and static types share the same symbol; only add 'typeof' for the static side. return symbolToTypeNode(symbol, context, isInstanceType); } // Always use 'typeof T' for type of class, enum, and module objects else if ( !forceExpansion && (symbol.flags & SymbolFlags.Class && !forceClassExpansion && !getBaseTypeVariableOfClass(symbol) && !(symbol.valueDeclaration && isClassLike(symbol.valueDeclaration) && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && (!isClassDeclaration(symbol.valueDeclaration) || isSymbolAccessible(symbol, context.enclosingDeclaration, isInstanceType, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible)) || symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || shouldWriteTypeOfFunctionSymbol()) ) { if (shouldExpandType(type, context)) { context.depth += 1; } else { return symbolToTypeNode(symbol, context, isInstanceType); } } if (context.visitedTypes?.has(typeId)) { // If type is an anonymous type literal in a type alias declaration, use type alias name const typeAlias = getTypeAliasForTypeLiteral(type); if (typeAlias) { // The specified symbol flags need to be reinterpreted as type flags return symbolToTypeNode(typeAlias, context, SymbolFlags.Type); } else { return createElidedInformationPlaceholder(context); } } else { return visitAndTransformType(type, createTypeNodeFromObjectType); } } else { // Anonymous types without a symbol are never circular. return createTypeNodeFromObjectType(type); } function shouldWriteTypeOfFunctionSymbol() { const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method some(symbol.declarations, declaration => isStatic(declaration) && !isLateBindableIndexSignature(getNameOfDeclaration(declaration)!)); const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && (symbol.parent || // is exported function symbol forEach(symbol.declarations, declaration => declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { // typeof is allowed only for static/non local functions return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedTypes?.has(typeId))) && // it is type of the symbol uses itself recursively (!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed } } } function visitAndTransformType(type: T, transform: (type: T) => TypeNode) { const typeId = type.id; const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; const id = getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference & T).node ? "N" + getNodeId((type as TypeReference & T).node!) : type.flags & TypeFlags.Conditional ? "N" + getNodeId((type as ConditionalType & T).root.node) : type.symbol ? (isConstructorObject ? "+" : "") + getSymbolId(type.symbol) : undefined; // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead // of types allows us to catch circular references to instantiations of the same anonymous type if (!context.visitedTypes) { context.visitedTypes = new Set(); } if (id && !context.symbolDepth) { context.symbolDepth = new Map(); } // Don't rely on type cache if we're expand a type, because we need to compute `canIncreaseExpansionDepth`. const links = context.maxExpansionDepth >= 0 ? undefined : context.enclosingDeclaration && getNodeLinks(context.enclosingDeclaration); const key = `${getTypeId(type)}|${context.flags}|${context.internalFlags}`; if (links) { links.serializedTypes ||= new Map(); } const cachedResult = links?.serializedTypes?.get(key); if (cachedResult) { // TODO:: check if we instead store late painted statements associated with this? cachedResult.trackedSymbols?.forEach( ([symbol, enclosingDeclaration, meaning]) => context.tracker.trackSymbol( symbol, enclosingDeclaration, meaning, ), ); if (cachedResult.truncating) { context.truncating = true; } context.approximateLength += cachedResult.addedLength; return deepCloneOrReuseNode(cachedResult.node); } let depth: number | undefined; if (id) { depth = context.symbolDepth!.get(id) || 0; if (depth > 10) { return createElidedInformationPlaceholder(context); } context.symbolDepth!.set(id, depth + 1); } context.visitedTypes.add(typeId); const prevTrackedSymbols = context.trackedSymbols; context.trackedSymbols = undefined; const startLength = context.approximateLength; const result = transform(type); const addedLength = context.approximateLength - startLength; if (!context.reportedDiagnostic && !context.encounteredError) { links?.serializedTypes?.set(key, { node: result, truncating: context.truncating, addedLength, trackedSymbols: context.trackedSymbols, }); } context.visitedTypes.delete(typeId); if (id) { context.symbolDepth!.set(id, depth!); } context.trackedSymbols = prevTrackedSymbols; return result; function deepCloneOrReuseNode(node: T): T { if (!nodeIsSynthesized(node) && getParseTreeNode(node) === node) { return node; } return setTextRange(context, factory.cloneNode(visitEachChildWorker(node, deepCloneOrReuseNode, /*context*/ undefined, deepCloneOrReuseNodes, deepCloneOrReuseNode)), node); } function deepCloneOrReuseNodes( nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number, ): NodeArray | undefined { if (nodes && nodes.length === 0) { // Ensure we explicitly make a copy of an empty array; visitNodes will not do this unless the array has elements, // which can lead to us reusing the same empty NodeArray more than once within the same AST during type noding. return setTextRangeWorker(factory.createNodeArray(/*elements*/ undefined, nodes.hasTrailingComma), nodes); } return visitNodes(nodes, visitor, test, start, count); } } function createTypeNodeFromObjectType(type: ObjectType): TypeNode { if (isGenericMappedType(type) || (type as MappedType).containsError) { return createMappedTypeNodeFromType(type as MappedType); } const resolved = resolveStructuredTypeMembers(type); if (!resolved.properties.length && !resolved.indexInfos.length) { if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { context.approximateLength += 2; return setEmitFlags(factory.createTypeLiteralNode(/*members*/ undefined), EmitFlags.SingleLine); } if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { const signature = resolved.callSignatures[0]; const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context) as FunctionTypeNode; return signatureNode; } if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { const signature = resolved.constructSignatures[0]; const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context) as ConstructorTypeNode; return signatureNode; } } const abstractSignatures = filter(resolved.constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract)); if (some(abstractSignatures)) { const types = map(abstractSignatures, getOrCreateTypeFromSignature); // count the number of type elements excluding abstract constructors const typeElementCount = resolved.callSignatures.length + (resolved.constructSignatures.length - abstractSignatures.length) + resolved.indexInfos.length + // exclude `prototype` when writing a class expression as a type literal, as per // the logic in `createTypeNodesFromResolvedType`. (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral ? countWhere(resolved.properties, p => !(p.flags & SymbolFlags.Prototype)) : length(resolved.properties)); // don't include an empty object literal if there were no other static-side // properties to write, i.e. `abstract class C { }` becomes `abstract new () => {}` // and not `(abstract new () => {}) & {}` if (typeElementCount) { // create a copy of the object type without any abstract construct signatures. types.push(getResolvedTypeWithoutAbstractConstructSignatures(resolved)); } return typeToTypeNodeHelper(getIntersectionType(types), context); } const restoreFlags = saveRestoreFlags(context); context.flags |= NodeBuilderFlags.InObjectTypeLiteral; const members = createTypeNodesFromResolvedType(resolved); restoreFlags(); const typeLiteralNode = factory.createTypeLiteralNode(members); context.approximateLength += 2; setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine); return typeLiteralNode; } function typeReferenceToTypeNode(type: TypeReference) { let typeArguments: readonly Type[] = getTypeArguments(type); if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context); return factory.createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); } const elementType = typeToTypeNodeHelper(typeArguments[0], context); const arrayType = factory.createArrayTypeNode(elementType); return type.target === globalArrayType ? arrayType : factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); } else if (type.target.objectFlags & ObjectFlags.Tuple) { typeArguments = sameMap(typeArguments, (t, i) => removeMissingType(t, !!((type.target as TupleType).elementFlags[i] & ElementFlags.Optional))); if (typeArguments.length > 0) { const arity = getTypeReferenceArity(type); const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context); if (tupleConstituentNodes) { const { labeledElementDeclarations } = type.target as TupleType; for (let i = 0; i 0) { let typeParameterCount = 0; if (type.target.typeParameters) { typeParameterCount = Math.min(type.target.typeParameters.length, typeArguments.length); // Maybe we should do this for more types, but for now we only elide type arguments that are // identical to their associated type parameters' defaults for `Iterable`, `IterableIterator`, // `AsyncIterable`, and `AsyncIterableIterator` to provide backwards-compatible .d.ts emit due // to each now having three type parameters instead of only one. if ( isReferenceToType(type, getGlobalIterableType(/*reportErrors*/ false)) || isReferenceToType(type, getGlobalIterableIteratorType(/*reportErrors*/ false)) || isReferenceToType(type, getGlobalAsyncIterableType(/*reportErrors*/ false)) || isReferenceToType(type, getGlobalAsyncIterableIteratorType(/*reportErrors*/ false)) ) { if ( !type.node || !isTypeReferenceNode(type.node) || !type.node.typeArguments || type.node.typeArguments.length 0) { const typeArgument = typeArguments[typeParameterCount - 1]; const typeParameter = type.target.typeParameters[typeParameterCount - 1]; const defaultType = getDefaultFromTypeParameter(typeParameter); if (!defaultType || !isTypeIdenticalTo(typeArgument, defaultType)) { break; } typeParameterCount--; } } } } typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context); } const restoreFlags = saveRestoreFlags(context); context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; const finalRef = symbolToTypeNode(type.symbol, context, SymbolFlags.Type, typeArgumentNodes); restoreFlags(); return !resultType ? finalRef : appendReferenceToType(resultType, finalRef as TypeReferenceNode); } } function appendReferenceToType(root: TypeReferenceNode | ImportTypeNode, ref: TypeReferenceNode): TypeReferenceNode | ImportTypeNode { if (isImportTypeNode(root)) { // first shift type arguments let typeArguments = root.typeArguments; let qualifier = root.qualifier; if (qualifier) { if (isIdentifier(qualifier)) { if (typeArguments !== getIdentifierTypeArguments(qualifier)) { qualifier = setIdentifierTypeArguments(factory.cloneNode(qualifier), typeArguments); } } else { if (typeArguments !== getIdentifierTypeArguments(qualifier.right)) { qualifier = factory.updateQualifiedName(qualifier, qualifier.left, setIdentifierTypeArguments(factory.cloneNode(qualifier.right), typeArguments)); } } } typeArguments = ref.typeArguments; // then move qualifiers const ids = getAccessStack(ref); for (const id of ids) { qualifier = qualifier ? factory.createQualifiedName(qualifier, id) : id; } return factory.updateImportTypeNode( root, root.argument, root.attributes, qualifier, typeArguments, root.isTypeOf, ); } else { // first shift type arguments let typeArguments = root.typeArguments; let typeName = root.typeName; if (isIdentifier(typeName)) { if (typeArguments !== getIdentifierTypeArguments(typeName)) { typeName = setIdentifierTypeArguments(factory.cloneNode(typeName), typeArguments); } } else { if (typeArguments !== getIdentifierTypeArguments(typeName.right)) { typeName = factory.updateQualifiedName(typeName, typeName.left, setIdentifierTypeArguments(factory.cloneNode(typeName.right), typeArguments)); } } typeArguments = ref.typeArguments; // then move qualifiers const ids = getAccessStack(ref); for (const id of ids) { typeName = factory.createQualifiedName(typeName, id); } return factory.updateTypeReferenceNode( root, typeName, typeArguments, ); } } function getAccessStack(ref: TypeReferenceNode): Identifier[] { let state = ref.typeName; const ids = []; while (!isIdentifier(state)) { ids.unshift(state.right); state = state.left; } ids.unshift(state); return ids; } function indexInfoToObjectComputedNamesOrSignatureDeclaration(indexInfo: IndexInfo, context: NodeBuilderContext, typeNode: TypeNode | undefined): [IndexSignatureDeclaration] | PropertySignature[] { if (indexInfo.components) { // Index info is derived from object or class computed property names (plus explicit named members) - we can clone those instead of writing out the result computed index signature const allComponentComputedNamesSerializable = every(indexInfo.components, e => { return !!(e.name && isComputedPropertyName(e.name) && isEntityNameExpression(e.name.expression) && context.enclosingDeclaration && isEntityNameVisible(e.name.expression, context.enclosingDeclaration, /*shouldComputeAliasToMakeVisible*/ false)?.accessibility === SymbolAccessibility.Accessible); }); if (allComponentComputedNamesSerializable) { // Only use computed name serialization form if all components are visible and take the `a.b.c` form const newComponents = filter(indexInfo.components, e => { // skip late bound props that contribute to the index signature - they'll be created by property creation anyway return !hasLateBindableName(e); }); return map(newComponents, e => { // Still need to track visibility even if we've already checked it to paint references as used trackComputedName(e.name.expression as EntityNameExpression, context.enclosingDeclaration, context); return setTextRange( context, factory.createPropertySignature( indexInfo.isReadonly ? [factory.createModifier(SyntaxKind.ReadonlyKeyword)] : undefined, e.name, (isPropertySignature(e) || isPropertyDeclaration(e) || isMethodSignature(e) || isMethodDeclaration(e) || isGetAccessor(e) || isSetAccessor(e)) && e.questionToken ? factory.createToken(SyntaxKind.QuestionToken) : undefined, typeNode || typeToTypeNodeHelper(getTypeOfSymbol(e.symbol), context), ), e, ); }); } } return [indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, typeNode)]; } function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined { if (checkTruncationLength(context)) { context.out.truncated = true; if (context.flags & NodeBuilderFlags.NoTruncation) { return [addSyntheticTrailingComment(factory.createNotEmittedTypeElement(), SyntaxKind.MultiLineCommentTrivia, "elided")]; } return [factory.createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined)]; } context.typeStack.push(-1); const typeElements: TypeElement[] = []; for (const signature of resolvedType.callSignatures) { typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context) as CallSignatureDeclaration); } for (const signature of resolvedType.constructSignatures) { if (signature.flags & SignatureFlags.Abstract) continue; typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context) as ConstructSignatureDeclaration); } for (const info of resolvedType.indexInfos) { typeElements.push(...indexInfoToObjectComputedNamesOrSignatureDeclaration(info, context, resolvedType.objectFlags & ObjectFlags.ReverseMapped ? createElidedInformationPlaceholder(context) : undefined)); } const properties = resolvedType.properties; if (!properties) { context.typeStack.pop(); return typeElements; } let i = 0; for (const propertySymbol of properties) { if (isExpanding(context) && propertySymbol.flags & SymbolFlags.Prototype) { continue; } i++; if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { if (propertySymbol.flags & SymbolFlags.Prototype) { continue; } if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); } if (isPrivateIdentifierSymbol(propertySymbol) && context.tracker.reportPrivateInBaseOfClassExpression) { context.tracker.reportPrivateInBaseOfClassExpression(idText((propertySymbol.valueDeclaration! as NamedDeclaration).name! as PrivateIdentifier)); } } if (checkTruncationLength(context) && (i + 2 (propertySymbol, SyntaxKind.PropertyDeclaration); if (propertyType !== writeType || propertySymbol.parent!.flags & SymbolFlags.Class && !propDeclaration) { const getterDeclaration = getDeclarationOfKind(propertySymbol, SyntaxKind.GetAccessor); if (getterDeclaration) { const getterSignature = getSignatureFromDeclaration(getterDeclaration); typeElements.push( setCommentRange( context, signatureToSignatureDeclarationHelper(symbolMapper ? instantiateSignature(getterSignature, symbolMapper) : getterSignature, SyntaxKind.GetAccessor, context, { name: propertyName }) as GetAccessorDeclaration, getterDeclaration, ), ); } const setterDeclaration = getDeclarationOfKind(propertySymbol, SyntaxKind.SetAccessor); if (setterDeclaration) { const setterSignature = getSignatureFromDeclaration(setterDeclaration); typeElements.push( setCommentRange( context, signatureToSignatureDeclarationHelper(symbolMapper ? instantiateSignature(setterSignature, symbolMapper) : setterSignature, SyntaxKind.SetAccessor, context, { name: propertyName }) as SetAccessorDeclaration, setterDeclaration, ), ); } return; } if (propertySymbol.parent!.flags & SymbolFlags.Class && propDeclaration && find(propDeclaration.modifiers, isAccessorModifier)) { const fakeGetterSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, propertyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); typeElements.push( setCommentRange( context, signatureToSignatureDeclarationHelper(fakeGetterSignature, SyntaxKind.GetAccessor, context, { name: propertyName }) as GetAccessorDeclaration, propDeclaration, ), ); const setterParam = createSymbol(SymbolFlags.FunctionScopedVariable, "arg" as __String); setterParam.links.type = writeType; const fakeSetterSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, [setterParam], voidType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); typeElements.push( signatureToSignatureDeclarationHelper(fakeSetterSignature, SyntaxKind.SetAccessor, context, { name: propertyName }) as SetAccessorDeclaration, ); return; } } } const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) { const signatures = getSignaturesOfType(filterType(propertyType, t => !(t.flags & TypeFlags.Undefined)), SignatureKind.Call); for (const signature of signatures) { const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context, { name: propertyName, questionToken: optionalToken }) as MethodSignature; typeElements.push(preserveCommentsOn(methodDeclaration, signature.declaration || propertySymbol.valueDeclaration)); } if (signatures.length || !optionalToken) { return; } } let propertyTypeNode: TypeNode; if (shouldUsePlaceholderForProperty(propertySymbol, context)) { propertyTypeNode = createElidedInformationPlaceholder(context); } else { if (propertyIsReverseMapped) { context.reverseMappedStack ||= []; context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol); } propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, /*declaration*/ undefined, propertyType, propertySymbol) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); if (propertyIsReverseMapped) { context.reverseMappedStack!.pop(); } } const modifiers = isReadonlySymbol(propertySymbol) ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined; if (modifiers) { context.approximateLength += 9; } const propertySignature = factory.createPropertySignature( modifiers, propertyName, optionalToken, propertyTypeNode, ); typeElements.push(preserveCommentsOn(propertySignature, propertySymbol.valueDeclaration)); function preserveCommentsOn(node: T, range: Node | undefined) { const jsdocPropertyTag = propertySymbol.declarations?.find((d): d is JSDocPropertyTag => d.kind === SyntaxKind.JSDocPropertyTag); if (jsdocPropertyTag) { const commentText = getTextOfJSDocComment(jsdocPropertyTag.comment); if (commentText) { setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]); } } else if (range) { // Copy comments to node for declaration emit setCommentRange(context, node, range); } return node; } } function setCommentRange(context: NodeBuilderContext, node: T, range: Node): T { if (context.enclosingFile && context.enclosingFile === getSourceFileOfNode(range)) { // Copy comments to node for declaration emit return setCommentRangeWorker(node, range); } return node; } function mapToTypeNodes(types: readonly Type[] | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined { if (some(types)) { if (checkTruncationLength(context)) { context.out.truncated = true; if (!isBareList) { return [ context.flags & NodeBuilderFlags.NoTruncation ? addSyntheticLeadingComment(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), SyntaxKind.MultiLineCommentTrivia, "elided") : factory.createTypeReferenceNode("...", /*typeArguments*/ undefined), ]; } else if (types.length > 2) { return [ typeToTypeNodeHelper(types[0], context), context.flags & NodeBuilderFlags.NoTruncation ? addSyntheticLeadingComment(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), SyntaxKind.MultiLineCommentTrivia, `... ${types.length - 2} more elided ...`) : factory.createTypeReferenceNode(`... ${types.length - 2} more ...`, /*typeArguments*/ undefined), typeToTypeNodeHelper(types[types.length - 1], context), ]; } } const mayHaveNameCollisions = !(context.flags & NodeBuilderFlags.UseFullyQualifiedType); /** Map from type reference identifier text to [type, index in `result` where the type node is] */ const seenNames = mayHaveNameCollisions ? createMultiMap<__string number>() : undefined; const result: TypeNode[] = []; let i = 0; for (const type of types) { i++; if (checkTruncationLength(context) && (i + 2 { if (!arrayIsHomogeneous(types, ([a], [b]) => typesAreSameReference(a, b))) { for (const [type, resultIndex] of types) { result[resultIndex] = typeToTypeNodeHelper(type, context); } } }); restoreFlags(); } return result; } } function typesAreSameReference(a: Type, b: Type): boolean { return a === b || !!a.symbol && a.symbol === b.symbol || !!a.aliasSymbol && a.aliasSymbol === b.aliasSymbol; } function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, context: NodeBuilderContext, typeNode: TypeNode | undefined): IndexSignatureDeclaration { const name = getNameFromIndexInfo(indexInfo) || "x"; const indexerTypeNode = typeToTypeNodeHelper(indexInfo.keyType, context); const indexingParameter = factory.createParameterDeclaration( /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, name, /*questionToken*/ undefined, indexerTypeNode, /*initializer*/ undefined, ); if (!typeNode) { typeNode = typeToTypeNodeHelper(indexInfo.type || anyType, context); } if (!indexInfo.type && !(context.flags & NodeBuilderFlags.AllowEmptyIndexInfoType)) { context.encounteredError = true; } context.approximateLength += name.length + 4; return factory.createIndexSignature( indexInfo.isReadonly ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined, [indexingParameter], typeNode, ); } interface SignatureToSignatureDeclarationOptions { modifiers?: readonly Modifier[]; name?: PropertyName; questionToken?: QuestionToken; } function signatureToSignatureDeclarationHelper(signature: Signature, kind: SignatureDeclaration["kind"], context: NodeBuilderContext, options?: SignatureToSignatureDeclarationOptions): SignatureDeclaration { let typeParameters: TypeParameterDeclaration[] | undefined; let typeArguments: TypeNode[] | undefined; const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; const cleanup = enterNewScope(context, signature.declaration, expandedParams, signature.typeParameters, signature.parameters, signature.mapper); context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) { typeArguments = signature.target.typeParameters.map(parameter => typeToTypeNodeHelper(instantiateType(parameter, signature.mapper), context)); } else { typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context)); } const restoreFlags = saveRestoreFlags(context); context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // SuppressAnyReturnType should only apply to the signature `return` position // If the expanded parameter list had a variadic in a non-trailing position, don't expand it const parameters = (some(expandedParams, p => p !== expandedParams[expandedParams.length - 1] && !!(getCheckFlags(p) & CheckFlags.RestParameter)) ? signature.parameters : expandedParams).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor)); const thisParameter = context.flags & NodeBuilderFlags.OmitThisParameter ? undefined : tryGetThisParameterDeclaration(signature, context); if (thisParameter) { parameters.unshift(thisParameter); } restoreFlags(); const returnTypeNode = serializeReturnTypeForSignature(context, signature); let modifiers = options?.modifiers; if ((kind === SyntaxKind.ConstructorType) && signature.flags & SignatureFlags.Abstract) { const flags = modifiersToFlags(modifiers); modifiers = factory.createModifiersFromModifierFlags(flags | ModifierFlags.Abstract); } const node = kind === SyntaxKind.CallSignature ? factory.createCallSignature(typeParameters, parameters, returnTypeNode) : kind === SyntaxKind.ConstructSignature ? factory.createConstructSignature(typeParameters, parameters, returnTypeNode) : kind === SyntaxKind.MethodSignature ? factory.createMethodSignature(modifiers, options?.name ?? factory.createIdentifier(""), options?.questionToken, typeParameters, parameters, returnTypeNode) : kind === SyntaxKind.MethodDeclaration ? factory.createMethodDeclaration(modifiers, /*asteriskToken*/ undefined, options?.name ?? factory.createIdentifier(""), /*questionToken*/ undefined, typeParameters, parameters, returnTypeNode, /*body*/ undefined) : kind === SyntaxKind.Constructor ? factory.createConstructorDeclaration(modifiers, parameters, /*body*/ undefined) : kind === SyntaxKind.GetAccessor ? factory.createGetAccessorDeclaration(modifiers, options?.name ?? factory.createIdentifier(""), parameters, returnTypeNode, /*body*/ undefined) : kind === SyntaxKind.SetAccessor ? factory.createSetAccessorDeclaration(modifiers, options?.name ?? factory.createIdentifier(""), parameters, /*body*/ undefined) : kind === SyntaxKind.IndexSignature ? factory.createIndexSignature(modifiers, parameters, returnTypeNode) : kind === SyntaxKind.JSDocFunctionType ? factory.createJSDocFunctionType(parameters, returnTypeNode) : kind === SyntaxKind.FunctionType ? factory.createFunctionTypeNode(typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : kind === SyntaxKind.ConstructorType ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : kind === SyntaxKind.FunctionDeclaration ? factory.createFunctionDeclaration(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, /*body*/ undefined) : kind === SyntaxKind.FunctionExpression ? factory.createFunctionExpression(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, factory.createBlock([])) : kind === SyntaxKind.ArrowFunction ? factory.createArrowFunction(modifiers, typeParameters, parameters, returnTypeNode, /*equalsGreaterThanToken*/ undefined, factory.createBlock([])) : Debug.assertNever(kind); if (typeArguments) { node.typeArguments = factory.createNodeArray(typeArguments); } if (signature.declaration?.kind === SyntaxKind.JSDocSignature && signature.declaration.parent.kind === SyntaxKind.JSDocOverloadTag) { const comment = getTextOfNode(signature.declaration.parent.parent, /*includeTrivia*/ true).slice(2, -2).split(/\r\n|\n|\r/).map(line => line.replace(/^\s+/, " ")).join("\n"); addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, comment, /*hasTrailingNewLine*/ true); } cleanup?.(); return node; } function createRecoveryBoundary(context: NodeBuilderContext) { if (cancellationToken && cancellationToken.throwIfCancellationRequested) { cancellationToken.throwIfCancellationRequested(); } let trackedSymbols: TrackedSymbol[]; let unreportedErrors: (() => void)[]; let hadError = false; const oldTracker = context.tracker; const oldTrackedSymbols = context.trackedSymbols; context.trackedSymbols = undefined; const oldEncounteredError = context.encounteredError; context.tracker = new SymbolTrackerImpl(context, { ...oldTracker.inner, reportCyclicStructureError() { markError(() => oldTracker.reportCyclicStructureError()); }, reportInaccessibleThisError() { markError(() => oldTracker.reportInaccessibleThisError()); }, reportInaccessibleUniqueSymbolError() { markError(() => oldTracker.reportInaccessibleUniqueSymbolError()); }, reportLikelyUnsafeImportRequiredError(specifier) { markError(() => oldTracker.reportLikelyUnsafeImportRequiredError(specifier)); }, reportNonSerializableProperty(name) { markError(() => oldTracker.reportNonSerializableProperty(name)); }, reportPrivateInBaseOfClassExpression(propertyName) { markError(() => oldTracker.reportPrivateInBaseOfClassExpression(propertyName)); }, trackSymbol(sym, decl, meaning) { (trackedSymbols ??= []).push([sym, decl, meaning]); return false; }, moduleResolverHost: context.tracker.moduleResolverHost, }, context.tracker.moduleResolverHost); return { startRecoveryScope, finalizeBoundary, markError, hadError: () => hadError, }; function markError(unreportedError?: () => void) { hadError = true; if (unreportedError) { (unreportedErrors ??= []).push(unreportedError); } } function startRecoveryScope() { const trackedSymbolsTop = trackedSymbols?.length ?? 0; const unreportedErrorsTop = unreportedErrors?.length ?? 0; return () => { hadError = false; // Reset the tracked symbols to before the error if (trackedSymbols) { trackedSymbols.length = trackedSymbolsTop; } if (unreportedErrors) { unreportedErrors.length = unreportedErrorsTop; } }; } function finalizeBoundary() { context.tracker = oldTracker; context.trackedSymbols = oldTrackedSymbols; context.encounteredError = oldEncounteredError; unreportedErrors?.forEach(fn => fn()); if (hadError) { return false; } trackedSymbols?.forEach( ([symbol, enclosingDeclaration, meaning]) => context.tracker.trackSymbol( symbol, enclosingDeclaration, meaning, ), ); return true; } } function enterNewScope( context: NodeBuilderContext, declaration: IntroducesNewScopeNode | ConditionalTypeNode | undefined, expandedParams: readonly Symbol[] | undefined, typeParameters: readonly TypeParameter[] | undefined, originalParameters?: readonly Symbol[] | undefined, mapper?: TypeMapper, ) { const cleanupContext = cloneNodeBuilderContext(context); // For regular function/method declarations, the enclosing declaration will already be signature.declaration, // so this is a no-op, but for arrow functions and function expressions, the enclosing declaration will be // the declaration that the arrow function / function expression is assigned to. // // If the parameters or return type include "typeof globalThis.paramName", using the wrong scope will lead // us to believe that we can emit "typeof paramName" instead, even though that would refer to the parameter, // not the global. Make sure we are in the right scope by changing the enclosingDeclaration to the function. // // We can't use the declaration directly; it may be in another file and so we may lose access to symbols // accessible to the current enclosing declaration, or gain access to symbols not accessible to the current // enclosing declaration. To keep this chain accurate, insert a fake scope into the chain which makes the // function's parameters visible. let cleanupParams: (() => void) | undefined; let cleanupTypeParams: (() => void) | undefined; const oldEnclosingDecl = context.enclosingDeclaration; const oldMapper = context.mapper; if (mapper) { context.mapper = mapper; } if (context.enclosingDeclaration && declaration) { // As a performance optimization, reuse the same fake scope within this chain. // This is especially needed when we are working on an excessively deep type; // if we don't do this, then we spend all of our time adding more and more // scopes that need to be searched in isSymbolAccessible later. Since all we // really want to do is to mark certain names as unavailable, we can just keep // all of the names we're introducing in one large table and push/pop from it as // needed; isSymbolAccessible will walk upward and find the closest "fake" scope, // which will conveniently report on any and all faked scopes in the chain. // // It'd likely be better to store this somewhere else for isSymbolAccessible, but // since that API _only_ uses the enclosing declaration (and its parents), this is // seems like the best way to inject names into that search process. // // Note that we only check the most immediate enclosingDeclaration; the only place we // could potentially add another fake scope into the chain is right here, so we don't // traverse all ancestors. cleanupParams = !some(expandedParams) ? undefined : pushFakeScope( "params", add => { if (!expandedParams) return; for (let pIndex = 0; pIndex { if (isParameter(d) && isBindingPattern(d.name)) { bindPattern(d.name); return true; } return undefined; function bindPattern(p: BindingPattern): void { forEach(p.elements, e => { switch (e.kind) { case SyntaxKind.OmittedExpression: return; case SyntaxKind.BindingElement: return bindElement(e); default: return Debug.assertNever(e); } }); } function bindElement(e: BindingElement): void { if (isBindingPattern(e.name)) { return bindPattern(e.name); } const symbol = getSymbolOfDeclaration(e); add(symbol.escapedName, symbol); } }) ) { add(param.escapedName, param); } } }, ); if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && some(typeParameters)) { cleanupTypeParams = pushFakeScope( "typeParams", add => { for (const typeParam of typeParameters ?? emptyArray) { const typeParamName = typeParameterToName(typeParam, context).escapedText; add(typeParamName, typeParam.symbol); } }, ); } function pushFakeScope(kind: "params" | "typeParams", addAll: (addSymbol: (name: __String, symbol: Symbol) => void) => void) { // We only ever need to look two declarations upward. Debug.assert(context.enclosingDeclaration); let existingFakeScope: Node | undefined; if (getNodeLinks(context.enclosingDeclaration).fakeScopeForSignatureDeclaration === kind) { existingFakeScope = context.enclosingDeclaration; } else if (context.enclosingDeclaration.parent && getNodeLinks(context.enclosingDeclaration.parent).fakeScopeForSignatureDeclaration === kind) { existingFakeScope = context.enclosingDeclaration.parent; } Debug.assertOptionalNode(existingFakeScope, isBlock); const locals = existingFakeScope?.locals ?? createSymbolTable(); let newLocals: __String[] | undefined; let oldLocals: { name: __String; oldSymbol: Symbol; }[] | undefined; addAll((name, symbol) => { // Add cleanup information only if we don't own the fake scope if (existingFakeScope) { const oldSymbol = locals.get(name); if (!oldSymbol) { newLocals = append(newLocals, name); } else { oldLocals = append(oldLocals, { name, oldSymbol }); } } locals.set(name, symbol); }); if (!existingFakeScope) { // Use a Block for this; the type of the node doesn't matter so long as it // has locals, and this is cheaper/easier than using a function-ish Node. const fakeScope = factory.createBlock(emptyArray); getNodeLinks(fakeScope).fakeScopeForSignatureDeclaration = kind; fakeScope.locals = locals; setParent(fakeScope, context.enclosingDeclaration); context.enclosingDeclaration = fakeScope; } else { // We did not create the current scope, so we have to clean it up return function undo() { forEach(newLocals, s => locals.delete(s)); forEach(oldLocals, s => locals.set(s.name, s.oldSymbol)); }; } } } return () => { cleanupParams?.(); cleanupTypeParams?.(); cleanupContext(); context.enclosingDeclaration = oldEnclosingDecl; context.mapper = oldMapper; }; } function tryGetThisParameterDeclaration(signature: Signature, context: NodeBuilderContext) { if (signature.thisParameter) { return symbolToParameterDeclaration(signature.thisParameter, context); } if (signature.declaration && isInJSFile(signature.declaration)) { const thisTag = getJSDocThisTag(signature.declaration); if (thisTag && thisTag.typeExpression) { return factory.createParameterDeclaration( /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "this", /*questionToken*/ undefined, typeToTypeNodeHelper(getTypeFromTypeNode(context, thisTag.typeExpression), context), ); } } } function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration { const restoreFlags = saveRestoreFlags(context); context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic const modifiers = factory.createModifiersFromModifierFlags(getTypeParameterModifiers(type)); const name = typeParameterToName(type, context); const defaultParameter = getDefaultFromTypeParameter(type); const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); restoreFlags(); return factory.createTypeParameterDeclaration(modifiers, name, constraintNode, defaultParameterNode); } function typeToTypeNodeHelperWithPossibleReusableTypeNode(type: Type, typeNode: TypeNode | undefined, context: NodeBuilderContext) { return !canPossiblyExpandType(type, context) && typeNode && getTypeFromTypeNode(context, typeNode) === type && syntacticNodeBuilder.tryReuseExistingTypeNode(context, typeNode) || typeToTypeNodeHelper(type, context); } function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { const constraintNode = constraint && typeToTypeNodeHelperWithPossibleReusableTypeNode(constraint, getConstraintDeclaration(type), context); return typeParameterToDeclarationWithConstraint(type, context, constraintNode); } function typePredicateToTypePredicateNodeHelper(typePredicate: TypePredicate, context: NodeBuilderContext): TypePredicateNode { const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createToken(SyntaxKind.AssertsKeyword) : undefined; const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? setEmitFlags(factory.createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) : factory.createThisTypeNode(); const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context); return factory.createTypePredicateNode(assertsModifier, parameterName, typeNode); } function getEffectiveParameterDeclaration(parameterSymbol: Symbol): ParameterDeclaration | JSDocParameterTag | undefined { const parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); if (parameterDeclaration) { return parameterDeclaration; } if (!isTransientSymbol(parameterSymbol)) { return getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); } } function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration { const parameterDeclaration = getEffectiveParameterDeclaration(parameterSymbol); const parameterType = getTypeOfSymbol(parameterSymbol); const parameterTypeNode = serializeTypeForDeclaration(context, parameterDeclaration, parameterType, parameterSymbol); const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && preserveModifierFlags && parameterDeclaration && canHaveModifiers(parameterDeclaration) ? map(getModifiers(parameterDeclaration), factory.cloneNode) : undefined; const isRest = parameterDeclaration && isRestParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.RestParameter; const dotDotDotToken = isRest ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined; const name = parameterToParameterDeclarationName(parameterSymbol, parameterDeclaration, context); const isOptional = parameterDeclaration && isOptionalParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.OptionalParameter; const questionToken = isOptional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; const parameterNode = factory.createParameterDeclaration( modifiers, dotDotDotToken, name, questionToken, parameterTypeNode, /*initializer*/ undefined, ); context.approximateLength += symbolName(parameterSymbol).length + 3; return parameterNode; } function parameterToParameterDeclarationName(parameterSymbol: Symbol, parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined, context: NodeBuilderContext) { return parameterDeclaration ? parameterDeclaration.name ? parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(factory.cloneNode(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) : parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(factory.cloneNode(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) : cloneBindingName(parameterDeclaration.name) : symbolName(parameterSymbol) : symbolName(parameterSymbol); function cloneBindingName(node: BindingName): BindingName { return elideInitializerAndSetEmitFlags(node) as BindingName; function elideInitializerAndSetEmitFlags(node: Node): Node { if (context.tracker.canTrackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { trackComputedName(node.expression, context.enclosingDeclaration, context); } let visited = visitEachChildWorker(node, elideInitializerAndSetEmitFlags, /*context*/ undefined, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags); if (isBindingElement(visited)) { visited = factory.updateBindingElement( visited, visited.dotDotDotToken, visited.propertyName, visited.name, /*initializer*/ undefined, ); } if (!nodeIsSynthesized(visited)) { visited = factory.cloneNode(visited); } return setEmitFlags(visited, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping); } } } function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { if (!context.tracker.canTrackSymbol) return; // get symbol of the first identifier of the entityName const firstIdentifier = getFirstIdentifier(accessExpression); const name = resolveName(enclosingDeclaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); if (name) { context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); } else { // Name does not resolve at target location, track symbol at dest location (should be inaccessible) const fallback = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); if (fallback) { context.tracker.trackSymbol(fallback, enclosingDeclaration, SymbolFlags.Value); } } } function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { context.tracker.trackSymbol(symbol, context.enclosingDeclaration, meaning); return lookupSymbolChainWorker(symbol, context, meaning, yieldModuleSymbol); } function lookupSymbolChainWorker(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. let chain: Symbol[]; const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; if (!isTypeParameter && (context.enclosingDeclaration || context.flags & NodeBuilderFlags.UseFullyQualifiedType) && !(context.internalFlags & InternalNodeBuilderFlags.DoNotIncludeSymbolChain)) { chain = Debug.checkDefined(getSymbolChain(symbol, meaning, /*endOfChain*/ true)); Debug.assert(chain && chain.length > 0); } else { chain = [symbol]; } return chain; /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, !!(context.flags & NodeBuilderFlags.UseOnlyExternalAliasing)); let parentSpecifiers: (string | undefined)[]; if ( !accessibleSymbolChain || needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning)) ) { // Go up and add our parent. const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration, meaning); if (length(parents)) { parentSpecifiers = parents!.map(symbol => some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol) ? getSpecifierForModuleSymbol(symbol, context) : undefined ); const indices = parents!.map((_, i) => i); indices.sort(sortByBestName); const sortedParents = indices.map(i => parents![i]); for (const parent of sortedParents) { const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); if (parentChain) { if ( parent.exports && parent.exports.get(InternalSymbolName.ExportEquals) && getSymbolIfSameReference(parent.exports.get(InternalSymbolName.ExportEquals)!, symbol) ) { // parentChain root _is_ symbol - symbol is a module export=, so it kinda looks like it's own parent // No need to lookup an alias for the symbol in itself accessibleSymbolChain = parentChain; break; } accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]); break; } } } } if (accessibleSymbolChain) { return accessibleSymbolChain; } if ( // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. endOfChain || // If a parent symbol is an anonymous type, don't write it. !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral)) ) { // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) if (!endOfChain && !yieldModuleSymbol && !!forEach(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { return; } return [symbol]; } function sortByBestName(a: number, b: number) { const specifierA = parentSpecifiers[a]; const specifierB = parentSpecifiers[b]; if (specifierA && specifierB) { const isBRelative = pathIsRelative(specifierB); if (pathIsRelative(specifierA) === isBRelative) { // Both relative or both non-relative, sort by number of parts return moduleSpecifiers.countPathComponents(specifierA) - moduleSpecifiers.countPathComponents(specifierB); } if (isBRelative) { // A is non-relative, B is relative: prefer A return -1; } // A is relative, B is non-relative: prefer B return 1; } return 0; } } } function typeParametersToTypeParameterDeclarations(symbol: Symbol, context: NodeBuilderContext) { let typeParameterNodes: NodeArray | undefined; const targetSymbol = getTargetSymbol(symbol); if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { typeParameterNodes = factory.createNodeArray(map(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), tp => typeParameterToDeclaration(tp, context))); } return typeParameterNodes; } function lookupTypeParameterNodes(chain: Symbol[], index: number, context: NodeBuilderContext) { Debug.assert(chain && 0 getMappedType(t, (nextSymbol as TransientSymbol).links.mapper!)), context); } else { typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); } } return typeParameterNodes; } /** * Given A[B][C][D], finds A[B] */ function getTopmostIndexedAccessType(top: IndexedAccessTypeNode): IndexedAccessTypeNode { if (isIndexedAccessTypeNode(top.objectType)) { return getTopmostIndexedAccessType(top.objectType); } return top; } function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext, overrideImportMode?: ResolutionMode) { let file = getDeclarationOfKind(symbol, SyntaxKind.SourceFile); if (!file) { const equivalentFileSymbol = firstDefined(symbol.declarations, d => getFileSymbolIfFileSymbolExportEqualsContainer(d, symbol)); if (equivalentFileSymbol) { file = getDeclarationOfKind(equivalentFileSymbol, SyntaxKind.SourceFile); } } if (file && file.moduleName !== undefined) { // Use the amd name if it is available return file.moduleName; } if (!file) { if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); } } if (!context.enclosingFile || !context.tracker.moduleResolverHost) { // If there's no context declaration, we can't lookup a non-ambient specifier, so we just use the symbol name if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); } return getSourceFileOfNode(getNonAugmentationDeclaration(symbol)!).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full } const enclosingDeclaration = getOriginalNode(context.enclosingDeclaration); const originalModuleSpecifier = canHaveModuleSpecifier(enclosingDeclaration) ? tryGetModuleSpecifierFromDeclaration(enclosingDeclaration) : undefined; const contextFile = context.enclosingFile; const resolutionMode = overrideImportMode || originalModuleSpecifier && host.getModeForUsageLocation(contextFile, originalModuleSpecifier) || contextFile && host.getDefaultResolutionModeForFile(contextFile); const cacheKey = createModeAwareCacheKey(contextFile.path, resolutionMode); const links = getSymbolLinks(symbol); let specifier = links.specifierCache && links.specifierCache.get(cacheKey); if (!specifier) { const isBundle = !!compilerOptions.outFile; // For declaration bundles, we need to generate absolute paths relative to the common source dir for imports, // just like how the declaration emitter does for the ambient module declarations - we can easily accomplish this // using the `baseUrl` compiler option (which we would otherwise never use in declaration emit) and a non-relative // specifier preference const { moduleResolverHost } = context.tracker; const specifierCompilerOptions = isBundle ? { ...compilerOptions, baseUrl: moduleResolverHost.getCommonSourceDirectory() } : compilerOptions; specifier = first(moduleSpecifiers.getModuleSpecifiers( symbol, checker, specifierCompilerOptions, contextFile, moduleResolverHost, { importModuleSpecifierPreference: isBundle ? "non-relative" : "project-relative", importModuleSpecifierEnding: isBundle ? "minimal" : resolutionMode === ModuleKind.ESNext ? "js" : undefined, }, { overrideImportMode }, )); links.specifierCache ??= new Map(); links.specifierCache.set(cacheKey, specifier); } return specifier; } function symbolToEntityNameNode(symbol: Symbol): EntityName { const identifier = factory.createIdentifier(unescapeLeadingUnderscores(symbol.escapedName)); return symbol.parent ? factory.createQualifiedName(symbolToEntityNameNode(symbol.parent), identifier) : identifier; } function symbolToTypeNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode { const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module const isTypeOf = meaning === SymbolFlags.Value; if (some(chain[0].declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { // module is root, must use `ImportTypeNode` const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined; const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context); const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); const targetFile = getSourceFileOfModule(chain[0]); let specifier: string | undefined; let attributes: ImportAttributes | undefined; if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { // An `import` type directed at an esm format file is only going to resolve in esm mode - set the esm mode assertion if (targetFile?.impliedNodeFormat === ModuleKind.ESNext && targetFile.impliedNodeFormat !== contextFile?.impliedNodeFormat) { specifier = getSpecifierForModuleSymbol(chain[0], context, ModuleKind.ESNext); attributes = factory.createImportAttributes( factory.createNodeArray([ factory.createImportAttribute( factory.createStringLiteral("resolution-mode"), factory.createStringLiteral("import"), ), ]), ); } } if (!specifier) { specifier = getSpecifierForModuleSymbol(chain[0], context); } if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.includes("/node_modules/")) { const oldSpecifier = specifier; if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { // We might be able to write a portable import type using a mode override; try specifier generation again, but with a different mode set const swappedMode = contextFile?.impliedNodeFormat === ModuleKind.ESNext ? ModuleKind.CommonJS : ModuleKind.ESNext; specifier = getSpecifierForModuleSymbol(chain[0], context, swappedMode); if (specifier.includes("/node_modules/")) { // Still unreachable :( specifier = oldSpecifier; } else { attributes = factory.createImportAttributes( factory.createNodeArray([ factory.createImportAttribute( factory.createStringLiteral("resolution-mode"), factory.createStringLiteral(swappedMode === ModuleKind.ESNext ? "import" : "require"), ), ]), ); } } if (!attributes) { // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error // since declaration files with these kinds of references are liable to fail when published :( context.encounteredError = true; if (context.tracker.reportLikelyUnsafeImportRequiredError) { context.tracker.reportLikelyUnsafeImportRequiredError(oldSpecifier); } } } const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier)); context.approximateLength += specifier.length + 10; // specifier + import("") if (!nonRootParts || isEntityName(nonRootParts)) { if (nonRootParts) { const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); } return factory.createImportTypeNode(lit, attributes, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf); } else { const splitNode = getTopmostIndexedAccessType(nonRootParts); const qualifier = (splitNode.objectType as TypeReferenceNode).typeName; return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, attributes, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType); } } const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0); if (isIndexedAccessTypeNode(entityName)) { return entityName; // Indexed accesses can never be `typeof` } if (isTypeOf) { return factory.createTypeQueryNode(entityName); } else { const lastId = isIdentifier(entityName) ? entityName : entityName.right; const lastTypeArgs = getIdentifierTypeArguments(lastId); setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); } function createAccessFromSymbolChain(chain: Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode { const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context); const symbol = chain[index]; const parent = chain[index - 1]; let symbolName: string | undefined; if (index === 0) { context.flags |= NodeBuilderFlags.InInitialEntityName; symbolName = getNameOfSymbolAsWritten(symbol, context); context.approximateLength += (symbolName ? symbolName.length : 0) + 1; context.flags ^= NodeBuilderFlags.InInitialEntityName; } else { if (parent && getExportsOfSymbol(parent)) { const exports = getExportsOfSymbol(parent); forEachEntry(exports, (ex, name) => { if (getSymbolIfSameReference(ex, symbol) && !isLateBoundName(name) && name !== InternalSymbolName.ExportEquals) { symbolName = unescapeLeadingUnderscores(name); return true; } }); } } if (symbolName === undefined) { const name = firstDefined(symbol.declarations, getNameOfDeclaration); if (name && isComputedPropertyName(name) && isEntityName(name.expression)) { const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); if (isEntityName(LHS)) { return factory.createIndexedAccessTypeNode(factory.createParenthesizedType(factory.createTypeQueryNode(LHS)), factory.createTypeQueryNode(name.expression)); } return LHS; } symbolName = getNameOfSymbolAsWritten(symbol, context); } context.approximateLength += symbolName.length + 1; if ( !(context.flags & NodeBuilderFlags.ForbidIndexedAccessSymbolReferences) && parent && getMembersOfSymbol(parent) && getMembersOfSymbol(parent).get(symbol.escapedName) && getSymbolIfSameReference(getMembersOfSymbol(parent).get(symbol.escapedName)!, symbol) ) { // Should use an indexed access const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); if (isIndexedAccessTypeNode(LHS)) { return factory.createIndexedAccessTypeNode(LHS, factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); } else { return factory.createIndexedAccessTypeNode(factory.createTypeReferenceNode(LHS, typeParameterNodes as readonly TypeNode[]), factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); } } const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); identifier.symbol = symbol; if (index > stopper) { const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); if (!isEntityName(LHS)) { return Debug.fail("Impossible construct - an export of an indexed access cannot be reachable"); } return factory.createQualifiedName(LHS, identifier); } return identifier; } } function typeParameterShadowsOtherTypeParameterInScope(escapedName: __String, context: NodeBuilderContext, type: TypeParameter) { const result = resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); if (result && result.flags & SymbolFlags.TypeParameter) { return result !== type.symbol; } return false; } function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) { if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) { const cached = context.typeParameterNames.get(getTypeId(type)); if (cached) { return cached; } } let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); if (!(result.kind & SyntaxKind.Identifier)) { return factory.createIdentifier("(Missing type parameter)"); } const decl = type.symbol?.declarations?.[0]; if (decl && isTypeParameterDeclaration(decl)) { result = setTextRange(context, result, decl.name); } if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { const rawtext = result.escapedText as string; let i = context.typeParameterNamesByTextNextNameCount?.get(rawtext) || 0; let text = rawtext; while (context.typeParameterNamesByText?.has(text) || typeParameterShadowsOtherTypeParameterInScope(text as __String, context, type)) { i++; text = `${rawtext}_${i}`; } if (text !== rawtext) { const typeArguments = getIdentifierTypeArguments(result); result = factory.createIdentifier(text); setIdentifierTypeArguments(result, typeArguments); } if (context.mustCreateTypeParametersNamesLookups) { context.mustCreateTypeParametersNamesLookups = false; context.typeParameterNames = new Map(context.typeParameterNames); context.typeParameterNamesByTextNextNameCount = new Map(context.typeParameterNamesByTextNextNameCount); context.typeParameterNamesByText = new Set(context.typeParameterNamesByText); } // avoiding iterations of the above loop turns out to be worth it when `i` starts to get large, so we cache the max // `i` we've used thus far, to save work later context.typeParameterNamesByTextNextNameCount!.set(rawtext, i); context.typeParameterNames!.set(getTypeId(type), result); context.typeParameterNamesByText!.add(text); } return result; } function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier; function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName; function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName { const chain = lookupSymbolChain(symbol, context, meaning); if ( expectsIdentifier && chain.length !== 1 && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowQualifiedNameInPlaceOfIdentifier) ) { context.encounteredError = true; } return createEntityNameFromSymbolChain(chain, chain.length - 1); function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); const symbol = chain[index]; if (index === 0) { context.flags |= NodeBuilderFlags.InInitialEntityName; } const symbolName = getNameOfSymbolAsWritten(symbol, context); if (index === 0) { context.flags ^= NodeBuilderFlags.InInitialEntityName; } const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); identifier.symbol = symbol; return index > 0 ? factory.createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; } } function symbolToExpression(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { const chain = lookupSymbolChain(symbol, context, meaning); return createExpressionFromSymbolChain(chain, chain.length - 1); function createExpressionFromSymbolChain(chain: Symbol[], index: number): Expression { const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); const symbol = chain[index]; if (index === 0) { context.flags |= NodeBuilderFlags.InInitialEntityName; } let symbolName = getNameOfSymbolAsWritten(symbol, context); if (index === 0) { context.flags ^= NodeBuilderFlags.InInitialEntityName; } let firstChar = symbolName.charCodeAt(0); if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { const specifier = getSpecifierForModuleSymbol(symbol, context); context.approximateLength += 2 + specifier.length; // "specifier" return factory.createStringLiteral(specifier); } if (index === 0 || canUsePropertyAccess(symbolName, languageVersion)) { const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); identifier.symbol = symbol; context.approximateLength += 1 + symbolName.length; // .symbolName return index > 0 ? factory.createPropertyAccessExpression(createExpressionFromSymbolChain(chain, index - 1), identifier) : identifier; } else { if (firstChar === CharacterCodes.openBracket) { symbolName = symbolName.substring(1, symbolName.length - 1); firstChar = symbolName.charCodeAt(0); } let expression: Expression | undefined; if (isSingleOrDoubleQuote(firstChar) && !(symbol.flags & SymbolFlags.EnumMember)) { const literalText = stripQuotes(symbolName).replace(/\\./g, s => s.substring(1)); context.approximateLength += literalText.length + 2; // "literalText" expression = factory.createStringLiteral(literalText, firstChar === CharacterCodes.singleQuote); } else if (("" + +symbolName) === symbolName) { context.approximateLength += symbolName.length; // +symbolName expression = factory.createNumericLiteral(+symbolName); } if (!expression) { const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); identifier.symbol = symbol; context.approximateLength += symbolName.length; // symbolName expression = identifier; } context.approximateLength += 2; // [] return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression); } } } function isStringNamed(d: Declaration) { const name = getNameOfDeclaration(d); if (!name) { return false; } if (isComputedPropertyName(name)) { const type = checkExpression(name.expression); return !!(type.flags & TypeFlags.StringLike); } if (isElementAccessExpression(name)) { const type = checkExpression(name.argumentExpression); return !!(type.flags & TypeFlags.StringLike); } return isStringLiteral(name); } function isSingleQuotedStringNamed(d: Declaration) { const name = getNameOfDeclaration(d); return !!(name && isStringLiteral(name) && (name.singleQuote || !nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'"))); } function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { const hashPrivateName = getClonedHashPrivateName(symbol); if (hashPrivateName) { return hashPrivateName; } const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); const isMethod = !!(symbol.flags & SymbolFlags.Method); const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote, stringNamed, isMethod); if (fromNameType) { return fromNameType; } const rawName = unescapeLeadingUnderscores(symbol.escapedName); return createPropertyNameNodeForIdentifierOrLiteral(rawName, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod); } // See getNameForSymbolFromNameType for a stringy equivalent function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote: boolean, stringNamed: boolean, isMethod: boolean) { const nameType = getSymbolLinks(symbol).nameType; if (nameType) { if (nameType.flags & TypeFlags.StringOrNumberLiteral) { const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && (stringNamed || !isNumericLiteralName(name))) { return factory.createStringLiteral(name, !!singleQuote); } if (isNumericLiteralName(name) && startsWith(name, "-")) { return factory.createComputedPropertyName(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-name))); } return createPropertyNameNodeForIdentifierOrLiteral(name, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod); } if (nameType.flags & TypeFlags.UniqueESSymbol) { return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value)); } } } function cloneNodeBuilderContext(context: NodeBuilderContext) { // Make type parameters created within this context not consume the name outside this context // The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when // it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends // through the type tree, so the only cases where we could have used distinct sibling scopes was when there // were multiple generic overloads with similar generated type parameter names // The effect: // When we write out // export const x: (x: T) => T // export const y: (x: T) => T // we write it out like that, rather than as // export const x: (x: T) => T // export const y: (x: T_1) => T_1 const oldMustCreateTypeParameterSymbolList = context.mustCreateTypeParameterSymbolList; const oldMustCreateTypeParametersNamesLookups = context.mustCreateTypeParametersNamesLookups; context.mustCreateTypeParameterSymbolList = true; context.mustCreateTypeParametersNamesLookups = true; const oldTypeParameterNames = context.typeParameterNames; const oldTypeParameterNamesByText = context.typeParameterNamesByText; const oldTypeParameterNamesByTextNextNameCount = context.typeParameterNamesByTextNextNameCount; const oldTypeParameterSymbolList = context.typeParameterSymbolList; return () => { context.typeParameterNames = oldTypeParameterNames; context.typeParameterNamesByText = oldTypeParameterNamesByText; context.typeParameterNamesByTextNextNameCount = oldTypeParameterNamesByTextNextNameCount; context.typeParameterSymbolList = oldTypeParameterSymbolList; context.mustCreateTypeParameterSymbolList = oldMustCreateTypeParameterSymbolList; context.mustCreateTypeParametersNamesLookups = oldMustCreateTypeParametersNamesLookups; }; } function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration?: Node | undefined) { return symbol.declarations && find(symbol.declarations, s => !!getNonlocalEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); } function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { // In JS, you can say something like `Foo` and get a `Foo` implicitly - we don't want to preserve that original `Foo` in these cases, though. if (!(getObjectFlags(type) & ObjectFlags.Reference)) return true; if (!isTypeReferenceNode(existing)) return true; // `type` is a reference type, and `existing` is a type reference node, but we still need to make sure they refer to the _same_ target type // before we go comparing their type argument counts. void getTypeFromTypeReference(existing); // call to ensure symbol is resolved const symbol = getNodeLinks(existing).resolvedSymbol; const existingTarget = symbol && getDeclaredTypeOfSymbol(symbol); if (!existingTarget || existingTarget !== (type as TypeReference).target) return true; return length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); } function getEnclosingDeclarationIgnoringFakeScope(enclosingDeclaration: Node) { while (getNodeLinks(enclosingDeclaration).fakeScopeForSignatureDeclaration) { enclosingDeclaration = enclosingDeclaration.parent; } return enclosingDeclaration; } /** * Serialize the checker type for the declaration without trying to go though the syntactic printer * Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` */ function serializeInferredTypeForDeclaration(symbol: Symbol, context: NodeBuilderContext, type: Type) { if ( type.flags & TypeFlags.UniqueESSymbol && type.symbol === symbol && (!context.enclosingDeclaration || some(symbol.declarations, d => getSourceFileOfNode(d) === context.enclosingFile)) ) { context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; } const result = typeToTypeNodeHelper(type, context); return result; } /** * Serializes the type of the declaration by first trying to use the syntactic printer if possible and falling back to the checker type if not. * @param context - The node builder context. Any reused nodes are checked to be pulled from within the scope of the context's enclosingDeclaration. * @param declaration - The preferred declaration to pull existing type nodes from (the symbol will be used as a fallback to find any annotated declaration) * @param type - The type to write; an existing annotation must match this type if it's used, otherwise this is the type serialized as a new type node * @param symbol - The symbol is used both to find an existing annotation if declaration is not provided, and to determine if `unique symbol` should be printed */ function serializeTypeForDeclaration(context: NodeBuilderContext, declaration: Declaration | undefined, type: Type, symbol: Symbol) { let result; const addUndefinedForParameter = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration, context.enclosingDeclaration); const decl = declaration ?? symbol.valueDeclaration ?? getDeclarationWithTypeAnnotation(symbol) ?? symbol.declarations?.[0]; if (!canPossiblyExpandType(type, context) && decl) { const restore = addSymbolTypeToContext(context, symbol, type); if (isAccessor(decl)) { result = syntacticNodeBuilder.serializeTypeOfAccessor(decl, symbol, context); } else if ( hasInferredType(decl) && !nodeIsSynthesized(decl) && !(getObjectFlags(type) & ObjectFlags.RequiresWidening) ) { result = syntacticNodeBuilder.serializeTypeOfDeclaration(decl, symbol, context); } restore(); } if (!result) { if (addUndefinedForParameter) { type = getOptionalType(type); } result = serializeInferredTypeForDeclaration(symbol, context, type); } return result ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } function typeNodeIsEquivalentToType(annotatedDeclaration: Node | undefined, type: Type, typeFromTypeNode: Type) { if (typeFromTypeNode === type) { return true; } if (!annotatedDeclaration) { return false; } if ((isPropertySignature(annotatedDeclaration) || isPropertyDeclaration(annotatedDeclaration)) && annotatedDeclaration.questionToken) { return getTypeWithFacts(type, TypeFacts.NEUndefined) === typeFromTypeNode; } if (isParameter(annotatedDeclaration) && hasEffectiveQuestionToken(annotatedDeclaration)) { return getTypeWithFacts(type, TypeFacts.NEUndefined) === typeFromTypeNode; } return false; } /** * Serializes the return type of the signature by first trying to use the syntactic printer if possible and falling back to the checker type if not. */ function serializeReturnTypeForSignature(context: NodeBuilderContext, signature: Signature) { const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType; const restoreFlags = saveRestoreFlags(context); if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s let returnTypeNode: TypeNode | undefined; const returnType = getReturnTypeOfSignature(signature); if (!(suppressAny && isTypeAny(returnType))) { if (signature.declaration && !nodeIsSynthesized(signature.declaration) && !canPossiblyExpandType(returnType, context)) { const declarationSymbol = getSymbolOfDeclaration(signature.declaration); const restore = addSymbolTypeToContext(context, declarationSymbol, returnType); returnTypeNode = syntacticNodeBuilder.serializeReturnTypeForSignature(signature.declaration, declarationSymbol, context); restore(); } if (!returnTypeNode) { returnTypeNode = serializeInferredReturnTypeForSignature(context, signature, returnType); } } if (!returnTypeNode && !suppressAny) { returnTypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } restoreFlags(); return returnTypeNode; } /** * Serializes the checker type of the signature (does not use the syntactic printer) */ function serializeInferredReturnTypeForSignature(context: NodeBuilderContext, signature: Signature, returnType: Type) { const oldSuppressReportInferenceFallback = context.suppressReportInferenceFallback; context.suppressReportInferenceFallback = true; const typePredicate = getTypePredicateOfSignature(signature); const returnTypeNode = typePredicate ? typePredicateToTypePredicateNodeHelper(context.mapper ? instantiateTypePredicate(typePredicate, context.mapper) : typePredicate, context) : typeToTypeNodeHelper(returnType, context); context.suppressReportInferenceFallback = oldSuppressReportInferenceFallback; return returnTypeNode; } function trackExistingEntityName(node: T, context: NodeBuilderContext, enclosingDeclaration = context.enclosingDeclaration) { let introducesError = false; const leftmost = getFirstIdentifier(node); if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) { introducesError = true; return { introducesError, node }; } const meaning = getMeaningOfEntityNameReference(node); let sym: Symbol | undefined; if (isThisIdentifier(leftmost)) { // `this` isn't a bindable identifier - skip resolution, find a relevant `this` symbol directly and avoid exhaustive scope traversal sym = getSymbolOfDeclaration(getThisContainer(leftmost, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)); if (isSymbolAccessible(sym, leftmost, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { introducesError = true; context.tracker.reportInaccessibleThisError(); } return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; } sym = resolveEntityName(leftmost, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true); if ( context.enclosingDeclaration && !(sym && sym.flags & SymbolFlags.TypeParameter) ) { sym = getExportSymbolOfValueSymbolIfExported(sym); // Some declarations may be transplanted to a new location. // When this happens we need to make sure that the name has the same meaning at both locations // We also check for the unknownSymbol because when we create a fake scope some parameters may actually not be usable // either because they are the expanded rest parameter, // or because they are the newly added parameters from the tuple, which might have different meanings in the original context const symAtLocation = resolveEntityName(leftmost, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, context.enclosingDeclaration); if ( // Check for unusable parameters symbols symAtLocation === unknownSymbol || // If the symbol is not found, but was not found in the original scope either we probably have an error, don't reuse the node (symAtLocation === undefined && sym !== undefined) || // If the symbol is found both in declaration scope and in current scope then it should point to the same reference (symAtLocation && sym && !getSymbolIfSameReference(getExportSymbolOfValueSymbolIfExported(symAtLocation), sym)) ) { // In isolated declaration we will not do rest parameter expansion so there is no need to report on these. if (symAtLocation !== unknownSymbol) { context.tracker.reportInferenceFallback(node); } introducesError = true; return { introducesError, node, sym }; } else { sym = symAtLocation; } } if (sym) { // If a parameter is resolvable in the current context it is also visible, so no need to go to symbol accesibility if ( sym.flags & SymbolFlags.FunctionScopedVariable && sym.valueDeclaration ) { if (isPartOfParameterDeclaration(sym.valueDeclaration) || isJSDocParameterTag(sym.valueDeclaration)) { return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; } } if ( !(sym.flags & SymbolFlags.TypeParameter) && // Type parameters are visible in the current context if they are are resolvable !isDeclarationName(node) && isSymbolAccessible(sym, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible ) { context.tracker.reportInferenceFallback(node); introducesError = true; } else { context.tracker.trackSymbol(sym, enclosingDeclaration, meaning); } return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; } return { introducesError, node }; /** * Attaches a `.symbol` member to an identifier, cloning it to do so, so symbol information * is smuggled out for symbol display information. */ function attachSymbolToLeftmostIdentifier(node: Node): Node { if (node === leftmost) { const type = getDeclaredTypeOfSymbol(sym!); const name = sym!.flags & SymbolFlags.TypeParameter ? typeParameterToName(type, context) : factory.cloneNode(node as Identifier); name.symbol = sym!; // for quickinfo, which uses identifier symbol information return setTextRange(context, setEmitFlags(name, EmitFlags.NoAsciiEscaping), node); } const updated = visitEachChildWorker(node, c => attachSymbolToLeftmostIdentifier(c), /*context*/ undefined); return setTextRange(context, updated, node); } } function serializeTypeName(context: NodeBuilderContext, node: EntityName, isTypeOf?: boolean, typeArguments?: readonly TypeNode[]) { const meaning = isTypeOf ? SymbolFlags.Value : SymbolFlags.Type; const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); if (!symbol) return undefined; const resolvedSymbol = symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol; if (isSymbolAccessible(symbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) return undefined; return symbolToTypeNode(resolvedSymbol, context, meaning, typeArguments); } function canReuseTypeNode(context: NodeBuilderContext, existing: TypeNode) { const type = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true); if (!type) { return false; } if (isInJSFile(existing)) { if (isLiteralImportTypeNode(existing)) { // Ensure resolvedSymbol is present void getTypeFromImportTypeNode(existing); const nodeSymbol = getNodeLinks(existing).resolvedSymbol; return ( !nodeSymbol || !( // The import type resolved using jsdoc fallback logic (!existing.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) || // The import type had type arguments autofilled by js fallback logic !(length(existing.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol))) ) ); } } if (isTypeReferenceNode(existing)) { if (isConstTypeReference(existing)) return false; const symbol = getNodeLinks(existing).resolvedSymbol; if (!symbol) return false; if (symbol.flags & SymbolFlags.TypeParameter) { const declaredType = getDeclaredTypeOfSymbol(symbol); return !(context.mapper && getMappedType(declaredType, context.mapper) !== declaredType); } if (isInJSDoc(existing)) { return existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type) && !getIntendedTypeFromJSDocTypeReference(existing) // We should probably allow the reuse of JSDoc reference types such as String Number etc && !!(symbol.flags & SymbolFlags.Type); // JSDoc type annotations can reference values (meaning typeof value) as well as types. We only reuse type nodes } } if ( isTypeOperatorNode(existing) && existing.operator === SyntaxKind.UniqueKeyword && existing.type.kind === SyntaxKind.SymbolKeyword ) { const effectiveEnclosingContext = context.enclosingDeclaration && getEnclosingDeclarationIgnoringFakeScope(context.enclosingDeclaration); return !!findAncestor(existing, n => n === effectiveEnclosingContext); } return true; } function serializeExistingTypeNode(context: NodeBuilderContext, typeNode: TypeNode, addUndefined: boolean) { const type = getTypeFromTypeNode(context, typeNode); if ( addUndefined && !someType(type, t => !!(t.flags & TypeFlags.Undefined)) && canReuseTypeNode(context, typeNode) ) { const clone = syntacticNodeBuilder.tryReuseExistingTypeNode(context, typeNode); if (clone) { return factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); } } return typeToTypeNodeHelper(type, context); } function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext): Statement[] { const serializePropertySymbolForClass = makeSerializePropertySymbol(factory.createPropertyDeclaration, SyntaxKind.MethodDeclaration, /*useAccessors*/ true); const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((mods, name, question, type) => factory.createPropertySignature(mods, name, question, type), SyntaxKind.MethodSignature, /*useAccessors*/ false); // TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of // declaration mapping // We save the enclosing declaration off here so it's not adjusted by well-meaning declaration // emit codepaths which want to apply more specific contexts (so we can still refer to the root real declaration // we're trying to emit from later on) const enclosingDeclaration = context.enclosingDeclaration!; let results: Statement[] = []; const visitedSymbols = new Set(); const deferredPrivatesStack: Map[] = []; const oldcontext = context; context = { ...oldcontext, usedSymbolNames: new Set(oldcontext.usedSymbolNames), remappedSymbolNames: new Map(), remappedSymbolReferences: new Map(oldcontext.remappedSymbolReferences?.entries()), tracker: undefined!, }; const tracker: SymbolTracker = { ...oldcontext.tracker.inner, trackSymbol: (sym, decl, meaning) => { if (context.remappedSymbolNames?.has(getSymbolId(sym))) return false; // If the context has a remapped name for the symbol, it *should* mean it's been made visible const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*shouldComputeAliasesToMakeVisible*/ false); if (accessibleResult.accessibility === SymbolAccessibility.Accessible) { // Lookup the root symbol of the chain of refs we'll use to access it and serialize it const chain = lookupSymbolChainWorker(sym, context, meaning); if (!(sym.flags & SymbolFlags.Property)) { // Only include referenced privates in the same file. Weird JS aliases may expose privates // from other files - assume JS transforms will make those available via expected means const root = chain[0]; const contextFile = getSourceFileOfNode(oldcontext.enclosingDeclaration); if (some(root.declarations, d => getSourceFileOfNode(d) === contextFile)) { includePrivateSymbol(root); } } } else if (oldcontext.tracker.inner?.trackSymbol) { return oldcontext.tracker.inner.trackSymbol(sym, decl, meaning); } return false; }, }; context.tracker = new SymbolTrackerImpl(context, tracker, oldcontext.tracker.moduleResolverHost); forEachEntry(symbolTable, (symbol, name) => { const baseName = unescapeLeadingUnderscores(name); void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` }); let addingDeclare = !context.bundled; const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); if (exportEquals && symbolTable.size > 1 && exportEquals.flags & (SymbolFlags.Alias | SymbolFlags.Module)) { symbolTable = createSymbolTable(); // Remove extraneous elements from root symbol table (they'll be mixed back in when the target of the `export=` is looked up) symbolTable.set(InternalSymbolName.ExportEquals, exportEquals); } visitSymbolTable(symbolTable); return mergeRedundantStatements(results); function isIdentifierAndNotUndefined(node: Node | undefined): node is Identifier { return !!node && node.kind === SyntaxKind.Identifier; } function getNamesOfDeclaration(statement: Statement): Identifier[] { if (isVariableStatement(statement)) { return filter(map(statement.declarationList.declarations, getNameOfDeclaration), isIdentifierAndNotUndefined); } return filter([getNameOfDeclaration(statement as DeclarationStatement)], isIdentifierAndNotUndefined); } function flattenExportAssignedNamespace(statements: Statement[]) { const exportAssignment = find(statements, isExportAssignment); const nsIndex = findIndex(statements, isModuleDeclaration); let ns = nsIndex !== -1 ? statements[nsIndex] as ModuleDeclaration : undefined; if ( ns && exportAssignment && exportAssignment.isExportEquals && isIdentifier(exportAssignment.expression) && isIdentifier(ns.name) && idText(ns.name) === idText(exportAssignment.expression) && ns.body && isModuleBlock(ns.body) ) { // Pass 0: Correct situations where a module has both an `export = ns` and multiple top-level exports by stripping the export modifiers from // the top-level exports and exporting them in the targeted ns, as can occur when a js file has both typedefs and `module.export` assignments const excessExports = filter(statements, s => !!(getEffectiveModifierFlags(s) & ModifierFlags.Export)); const name = ns.name; let body = ns.body; if (length(excessExports)) { ns = factory.updateModuleDeclaration( ns, ns.modifiers, ns.name, body = factory.updateModuleBlock( body, factory.createNodeArray([ ...ns.body.statements, factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, id))), /*moduleSpecifier*/ undefined, ), ]), ), ); statements = [...statements.slice(0, nsIndex), ns, ...statements.slice(nsIndex + 1)]; } // Pass 1: Flatten `export namespace _exports {} export = _exports;` so long as the `export=` only points at a single namespace declaration if (!find(statements, s => s !== ns && nodeHasName(s, name))) { results = []; // If the namespace contains no export assignments or declarations, and no declarations flagged with `export`, then _everything_ is exported - // to respect this as the top level, we need to add an `export` modifier to everything const mixinExportFlag = !some(body.statements, s => hasSyntacticModifier(s, ModifierFlags.Export) || isExportAssignment(s) || isExportDeclaration(s)); forEach(body.statements, s => { addResult(s, mixinExportFlag ? ModifierFlags.Export : ModifierFlags.None); // Recalculates the ambient (and export, if applicable from above) flag }); statements = [...filter(statements, s => s !== ns && s !== exportAssignment), ...results]; } } return statements; } function mergeExportDeclarations(statements: Statement[]) { // Pass 2: Combine all `export {}` declarations const exports = filter(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; if (length(exports) > 1) { const nonExports = filter(statements, d => !isExportDeclaration(d) || !!d.moduleSpecifier || !d.exportClause); statements = [ ...nonExports, factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports(flatMap(exports, e => cast(e.exportClause, isNamedExports).elements)), /*moduleSpecifier*/ undefined, ), ]; } // Pass 2b: Also combine all `export {} from "..."` declarations as needed const reexports = filter(statements, d => isExportDeclaration(d) && !!d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; if (length(reexports) > 1) { const groups = group(reexports, decl => isStringLiteral(decl.moduleSpecifier!) ? ">" + decl.moduleSpecifier.text : ">"); if (groups.length !== reexports.length) { for (const group of groups) { if (group.length > 1) { // remove group members from statements and then merge group members and add back to statements statements = [ ...filter(statements, s => !group.includes(s as ExportDeclaration)), factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports(flatMap(group, e => cast(e.exportClause, isNamedExports).elements)), group[0].moduleSpecifier, ), ]; } } } } return statements; } function inlineExportModifiers(statements: Statement[]) { // Pass 3: Move all `export {}`'s to `export` modifiers where possible const index = findIndex(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !d.attributes && !!d.exportClause && isNamedExports(d.exportClause)); if (index >= 0) { const exportDecl = statements[index] as ExportDeclaration & { readonly exportClause: NamedExports; }; const replacements = mapDefined(exportDecl.exportClause.elements, e => { if (!e.propertyName && e.name.kind !== SyntaxKind.StringLiteral) { // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it const name = e.name; const indices = indicesOf(statements); const associatedIndices = filter(indices, i => nodeHasName(statements[i], name)); if (length(associatedIndices) && every(associatedIndices, i => canHaveExportModifier(statements[i]))) { for (const index of associatedIndices) { statements[index] = addExportModifier(statements[index] as Extract); } return undefined; } } return e; }); if (!length(replacements)) { // all clauses removed, remove the export declaration orderedRemoveItemAt(statements, index); } else { // some items filtered, others not - update the export declaration statements[index] = factory.updateExportDeclaration( exportDecl, exportDecl.modifiers, exportDecl.isTypeOnly, factory.updateNamedExports( exportDecl.exportClause, replacements, ), exportDecl.moduleSpecifier, exportDecl.attributes, ); } } return statements; } function mergeRedundantStatements(statements: Statement[]) { statements = flattenExportAssignedNamespace(statements); statements = mergeExportDeclarations(statements); statements = inlineExportModifiers(statements); // Not a cleanup, but as a final step: If there is a mix of `export` and non-`export` declarations, but no `export =` or `export {}` add a `export {};` so // declaration privacy is respected. if ( enclosingDeclaration && ((isSourceFile(enclosingDeclaration) && isExternalOrCommonJsModule(enclosingDeclaration)) || isModuleDeclaration(enclosingDeclaration)) && (!some(statements, isExternalModuleIndicator) || (!hasScopeMarker(statements) && some(statements, needsScopeMarker))) ) { statements.push(createEmptyExports(factory)); } return statements; } function addExportModifier(node: Extract) { const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; return factory.replaceModifiers(node, flags); } function removeExportModifier(node: Extract) { const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export; return factory.replaceModifiers(node, flags); } function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) { if (!suppressNewPrivateContext) { deferredPrivatesStack.push(new Map()); } let i = 0; const symbols = Array.from(symbolTable.values()); for (const symbol of symbols) { i++; if (checkTruncationLengthIfExpanding(context) && (i + 2 { serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias); }); deferredPrivatesStack.pop(); } } function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean): void { void getPropertiesOfType(getTypeOfSymbol(symbol)); // resolve symbol's type and properties, which should trigger any required merges // cache visited list based on merged symbol, since we want to use the unmerged top-level symbol, but // still skip reserializing it if we encounter the merged product later on const visitedSym = getMergedSymbol(symbol); if (visitedSymbols.has(getSymbolId(visitedSym))) { return; // Already printed } visitedSymbols.add(getSymbolId(visitedSym)); // Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration)))) { const scopeCleanup = cloneNodeBuilderContext(context); context.tracker.pushErrorFallbackNode(find(symbol.declarations, d => getSourceFileOfNode(d) === context.enclosingFile)); serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); context.tracker.popErrorFallbackNode(); scopeCleanup(); } } // Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias // or a merge of some number of those. // An interesting challenge is ensuring that when classes merge with namespaces and interfaces, is keeping // each symbol in only one of the representations // Also, synthesizing a default export of some kind // If it's an alias: emit `export default ref` // If it's a property: emit `export default _default` with a `_default` prop // If it's a class/interface/function: emit a class/interface/function with a `default` modifier // These forms can merge, eg (`export default 12; export default interface A {}`) function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean, escapedSymbolName = symbol.escapedName): void { const symbolName = unescapeLeadingUnderscores(escapedSymbolName); const isDefault = escapedSymbolName === InternalSymbolName.Default; if (isPrivate && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier) && isStringANonContextualKeyword(symbolName) && !isDefault) { // Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :( context.encounteredError = true; // TODO: Issue error via symbol tracker? return; // If we need to emit a private with a keyword name, we're done for, since something else will try to refer to it by that name } let needsPostExportDefault = isDefault && !!( symbol.flags & SymbolFlags.ExportDoesNotSupportDefaultModifier || (symbol.flags & SymbolFlags.Function && length(getPropertiesOfType(getTypeOfSymbol(symbol)))) ) && !(symbol.flags & SymbolFlags.Alias); // An alias symbol should preclude needing to make an alias ourselves let needsExportDeclaration = !needsPostExportDefault && !isPrivate && isStringANonContextualKeyword(symbolName) && !isDefault; // `serializeVariableOrProperty` will handle adding the export declaration if it is run (since `getInternalSymbolName` will create the name mapping), so we need to ensuer we unset `needsExportDeclaration` if it is if (needsPostExportDefault || needsExportDeclaration) { isPrivate = true; } const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0); const isConstMergedWithNS = symbol.flags & SymbolFlags.Module && symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) && escapedSymbolName !== InternalSymbolName.ExportEquals; const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol); if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) || isConstMergedWithNSPrintableAsSignatureMerge) { serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } if (symbol.flags & SymbolFlags.TypeAlias) { serializeTypeAlias(symbol, symbolName, modifierFlags); } // Need to skip over export= symbols below - json source files get a single `Property` flagged // symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is. if ( symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property | SymbolFlags.Accessor) && escapedSymbolName !== InternalSymbolName.ExportEquals && !(symbol.flags & SymbolFlags.Prototype) && !(symbol.flags & SymbolFlags.Class) && !(symbol.flags & SymbolFlags.Method) && !isConstMergedWithNSPrintableAsSignatureMerge ) { if (propertyAsAlias) { const createdExport = serializeMaybeAliasAssignment(symbol); if (createdExport) { needsExportDeclaration = false; needsPostExportDefault = false; } } else { const type = getTypeOfSymbol(symbol); const localName = getInternalSymbolName(symbol, symbolName); if (type.symbol && type.symbol !== symbol && type.symbol.flags & SymbolFlags.Function && some(type.symbol.declarations, isFunctionExpressionOrArrowFunction) && (type.symbol.members?.size || type.symbol.exports?.size)) { // assignment of a anonymous expando/class-like function, the func/ns/merge branch below won't trigger, // and the assignment form has to reference the unreachable anonymous type so will error. // Instead, serialize the type's symbol, but with the current symbol's name, rather than the anonymous one. if (!context.remappedSymbolReferences) { context.remappedSymbolReferences = new Map(); } context.remappedSymbolReferences.set(getSymbolId(type.symbol), symbol); // save name remapping as local name for target symbol serializeSymbolWorker(type.symbol, isPrivate, propertyAsAlias, escapedSymbolName); context.remappedSymbolReferences.delete(getSymbolId(type.symbol)); } else if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) { // If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags); } else { // A Class + Property merge is made for a `module.exports.Member = class {}`, and it doesn't serialize well as either a class _or_ a property symbol - in fact, _it behaves like an alias!_ // `var` is `FunctionScopedVariable`, `const` and `let` are `BlockScopedVariable`, and `module.exports.thing =` is `Property` const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable) ? symbol.parent?.valueDeclaration && isSourceFile(symbol.parent?.valueDeclaration) ? NodeFlags.Const // exports are immutable in es6, which is what we emulate and check; so it's safe to mark all exports as `const` (there's no difference to consumers, but it allows unique symbol type declarations) : undefined : isConstantVariable(symbol) ? NodeFlags.Const : NodeFlags.Let; const name = (needsPostExportDefault || !(symbol.flags & SymbolFlags.Property)) ? localName : getUnusedName(localName, symbol); let textRange: Node | undefined = symbol.declarations && find(symbol.declarations, d => isVariableDeclaration(d)); if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) { textRange = textRange.parent.parent; } const propertyAccessRequire = symbol.declarations?.find(isPropertyAccessExpression); if ( propertyAccessRequire && isBinaryExpression(propertyAccessRequire.parent) && isIdentifier(propertyAccessRequire.parent.right) && type.symbol?.valueDeclaration && isSourceFile(type.symbol.valueDeclaration) ) { const alias = localName === propertyAccessRequire.parent.right.escapedText ? undefined : propertyAccessRequire.parent.right; context.approximateLength += 12 + ((alias?.escapedText as string | undefined)?.length ?? 0); // `export { alias };` addResult( factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, localName)]), ), ModifierFlags.None, ); context.tracker.trackSymbol(type.symbol, context.enclosingDeclaration, SymbolFlags.Value); } else { const statement = setTextRange( context, factory.createVariableStatement( /*modifiers*/ undefined, factory.createVariableDeclarationList([ factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, /*declaration*/ undefined, type, symbol)), ], flags), ), textRange, ); context.approximateLength += 7 + name.length; // `var name: ;` addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags); if (name !== localName && !isPrivate) { // We rename the variable declaration we generate for Property symbols since they may have a name which // conflicts with a local declaration. For example, given input: // ``` // function g() {} // module.exports.g = g // ``` // In such a situation, we have a local variable named `g`, and a separate exported variable named `g`. // Naively, we would emit // ``` // function g() {} // export const g: typeof g; // ``` // That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but // the export declaration shadows it. // To work around that, we instead write // ``` // function g() {} // const g_1: typeof g; // export { g_1 as g }; // ``` // To create an export named `g` that does _not_ shadow the local `g` context.approximateLength += 16 + name.length + localName.length; // `export { name as localName };` addResult( factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, name, localName)]), ), ModifierFlags.None, ); needsExportDeclaration = false; needsPostExportDefault = false; } } } } } if (symbol.flags & SymbolFlags.Enum) { serializeEnum(symbol, symbolName, modifierFlags); } if (symbol.flags & SymbolFlags.Class) { if ( symbol.flags & SymbolFlags.Property && symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration.parent) && isClassExpression(symbol.valueDeclaration.parent.right) ) { // Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members, // since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property // _really_ acts like an Alias, and none of a BlockScopedVariable, Class, or Property. This is the travesty of JS binding today. serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } else { serializeAsClass(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } } if ((symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && (!isConstMergedWithNS || isTypeOnlyNamespace(symbol))) || isConstMergedWithNSPrintableAsSignatureMerge) { serializeModule(symbol, symbolName, modifierFlags); } // The class meaning serialization should handle serializing all interface members if (symbol.flags & SymbolFlags.Interface && !(symbol.flags & SymbolFlags.Class)) { serializeInterface(symbol, symbolName, modifierFlags); } if (symbol.flags & SymbolFlags.Alias) { serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } if (symbol.flags & SymbolFlags.Property && symbol.escapedName === InternalSymbolName.ExportEquals) { serializeMaybeAliasAssignment(symbol); } if (symbol.flags & SymbolFlags.ExportStar) { // synthesize export * from "moduleReference" // Straightforward - only one thing to do - make an export declaration if (symbol.declarations) { for (const node of symbol.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); if (!resolvedModule) continue; const isTypeOnly = (node as ExportDeclaration).isTypeOnly; const specifier = getSpecifierForModuleSymbol(resolvedModule, context); context.approximateLength += 17 + specifier.length; // `export * from "specifier";` addResult(factory.createExportDeclaration(/*modifiers*/ undefined, isTypeOnly, /*exportClause*/ undefined, factory.createStringLiteral(specifier)), ModifierFlags.None); } } } if (needsPostExportDefault) { const internalSymbolName = getInternalSymbolName(symbol, symbolName); context.approximateLength += 16 + internalSymbolName.length; // `export default internalName;` addResult(factory.createExportAssignment(/*modifiers*/ undefined, /*isExportEquals*/ false, factory.createIdentifier(internalSymbolName)), ModifierFlags.None); } else if (needsExportDeclaration) { const internalSymbolName = getInternalSymbolName(symbol, symbolName); context.approximateLength += 22 + symbolName.length + internalSymbolName.length; // `export { internalName as symbolName };` addResult( factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, internalSymbolName, symbolName)]), ), ModifierFlags.None, ); } } function includePrivateSymbol(symbol: Symbol) { if (some(symbol.declarations, isPartOfParameterDeclaration)) return; Debug.assertIsDefined(deferredPrivatesStack[deferredPrivatesStack.length - 1]); getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol // Blanket moving (import) aliases into the root private context should work, since imports are not valid within namespaces // (so they must have been in the root to begin with if they were real imports) cjs `require` aliases (an upcoming feature) // will throw a wrench in this, since those may have been nested, but we'll need to synthesize them in the outer scope // anyway, as that's the only place the import they translate to is valid. In such a case, we might need to use a unique name // for the moved import; which hopefully the above `getUnusedName` call should produce. const isExternalImportAlias = !!(symbol.flags & SymbolFlags.Alias) && !some(symbol.declarations, d => !!findAncestor(d, isExportDeclaration) || isNamespaceExport(d) || (isImportEqualsDeclaration(d) && !isExternalModuleReference(d.moduleReference))); deferredPrivatesStack[isExternalImportAlias ? 0 : (deferredPrivatesStack.length - 1)].set(getSymbolId(symbol), symbol); } function isExportingScope(enclosingDeclaration: Node) { return ((isSourceFile(enclosingDeclaration) && (isExternalOrCommonJsModule(enclosingDeclaration) || isJsonSourceFile(enclosingDeclaration))) || (isAmbientModule(enclosingDeclaration) && !isGlobalScopeAugmentation(enclosingDeclaration))); } // Prepends a `declare` and/or `export` modifier if the context requires it, and then adds `node` to `result` and returns `node` function addResult(node: Statement, additionalModifierFlags: ModifierFlags) { if (canHaveModifiers(node)) { const oldModifierFlags = getEffectiveModifierFlags(node); let newModifierFlags: ModifierFlags = ModifierFlags.None; const enclosingDeclaration = context.enclosingDeclaration && (isJSDocTypeAlias(context.enclosingDeclaration) ? getSourceFileOfNode(context.enclosingDeclaration) : context.enclosingDeclaration); if ( additionalModifierFlags & ModifierFlags.Export && enclosingDeclaration && (isExportingScope(enclosingDeclaration) || isModuleDeclaration(enclosingDeclaration)) && canHaveExportModifier(node) ) { // Classes, namespaces, variables, functions, interfaces, and types should all be `export`ed in a module context if not private newModifierFlags |= ModifierFlags.Export; } if ( addingDeclare && !(newModifierFlags & ModifierFlags.Export) && (!enclosingDeclaration || !(enclosingDeclaration.flags & NodeFlags.Ambient)) && (isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isModuleDeclaration(node)) ) { // Classes, namespaces, variables, enums, and functions all need `declare` modifiers to be valid in a declaration file top-level scope newModifierFlags |= ModifierFlags.Ambient; } if ((additionalModifierFlags & ModifierFlags.Default) && (isClassDeclaration(node) || isInterfaceDeclaration(node) || isFunctionDeclaration(node))) { newModifierFlags |= ModifierFlags.Default; } if (newModifierFlags) { node = factory.replaceModifiers(node, newModifierFlags | oldModifierFlags); } context.approximateLength += modifiersLength(newModifierFlags | oldModifierFlags); } results.push(node); } function serializeTypeAlias(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { const aliasType = getDeclaredTypeOfTypeAlias(symbol); const typeParams = getSymbolLinks(symbol).typeParameters; const typeParamDecls = map(typeParams, p => typeParameterToDeclaration(p, context)); const jsdocAliasDecl = symbol.declarations?.find(isJSDocTypeAlias); const commentText = getTextOfJSDocComment(jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined); const restoreFlags = saveRestoreFlags(context); context.flags |= NodeBuilderFlags.InTypeAlias; const oldEnclosingDecl = context.enclosingDeclaration; context.enclosingDeclaration = jsdocAliasDecl; const typeNode = jsdocAliasDecl && jsdocAliasDecl.typeExpression && isJSDocTypeExpression(jsdocAliasDecl.typeExpression) && syntacticNodeBuilder.tryReuseExistingTypeNode(context, jsdocAliasDecl.typeExpression.type) || typeToTypeNodeHelper(aliasType, context); const internalSymbolName = getInternalSymbolName(symbol, symbolName); context.approximateLength += 8 + (commentText?.length ?? 0) + internalSymbolName.length; // `/* comment */ type name = ...` addResult( setSyntheticLeadingComments( factory.createTypeAliasDeclaration(/*modifiers*/ undefined, internalSymbolName, typeParamDecls, typeNode), !commentText ? [] : [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }], ), modifierFlags, ); restoreFlags(); context.enclosingDeclaration = oldEnclosingDecl; } function serializeInterface(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { const internalSymbolName = getInternalSymbolName(symbol, symbolName); context.approximateLength += 14 + internalSymbolName.length; // `interface name { }` const interfaceType = getDeclaredTypeOfClassOrInterface(symbol); const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); const baseTypes = getBaseTypes(interfaceType); const baseType = length(baseTypes) ? getIntersectionType(baseTypes) : undefined; const members = serializePropertySymbolsForClassOrInterface(getPropertiesOfType(interfaceType), /*isClass*/ false, baseType); const callSignatures = serializeSignatures(SignatureKind.Call, interfaceType, baseType, SyntaxKind.CallSignature) as CallSignatureDeclaration[]; const constructSignatures = serializeSignatures(SignatureKind.Construct, interfaceType, baseType, SyntaxKind.ConstructSignature) as ConstructSignatureDeclaration[]; const indexSignatures = serializeIndexSignatures(interfaceType, baseType); const heritageClauses = !length(baseTypes) ? undefined : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, mapDefined(baseTypes, b => trySerializeAsTypeReference(b, SymbolFlags.Value)))]; addResult( factory.createInterfaceDeclaration( /*modifiers*/ undefined, internalSymbolName, typeParamDecls, heritageClauses, [...indexSignatures, ...constructSignatures, ...callSignatures, ...members], ), modifierFlags, ); } function serializePropertySymbolsForClassOrInterface(props: readonly Symbol[], isClass: false, baseType: Type | undefined): TypeElement[]; function serializePropertySymbolsForClassOrInterface(props: readonly Symbol[], isClass: true, baseType: Type | undefined, isStatic: boolean): ClassElement[]; function serializePropertySymbolsForClassOrInterface(props: readonly Symbol[], isClass: boolean, baseType: Type | undefined, isStatic?: boolean): (ClassElement | TypeElement)[] { const elements: (ClassElement | TypeElement)[] = []; let i = 0; for (const prop of props) { i++; if (checkTruncationLengthIfExpanding(context) && (i + 2 isNamespaceMember(m) && isIdentifierText(m.escapedName as string, ScriptTarget.ESNext)); } function isTypeOnlyNamespace(symbol: Symbol) { return every(getNamespaceMembersForSerialization(symbol), m => !(getSymbolFlags(resolveSymbol(m)) & SymbolFlags.Value)); } function serializeModule(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { const members = getNamespaceMembersForSerialization(symbol); const expanding = isExpanding(context); // Split NS members up by declaration - members whose parent symbol is the ns symbol vs those whose is not (but were added in later via merging) const locationMap = arrayToMultiMap(members, m => m.parent && m.parent === symbol || expanding ? "real" : "merged"); const realMembers = locationMap.get("real") || emptyArray; const mergedMembers = locationMap.get("merged") || emptyArray; // TODO: `suppressNewPrivateContext` is questionable -we need to simply be emitting privates in whatever scope they were declared in, rather // than whatever scope we traverse to them in. That's a bit of a complex rewrite, since we're not _actually_ tracking privates at all in advance, // so we don't even have placeholders to fill in. if (length(realMembers) || expanding) { let localName: ModuleName; if (expanding) { // Use the same name as symbol display. const oldFlags = context.flags; context.flags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName | SymbolFormatFlags.UseOnlyExternalAliasing; localName = symbolToNode(symbol, context, /*meaning*/ SymbolFlags.All) as ModuleName; context.flags = oldFlags; } else { const localText = getInternalSymbolName(symbol, symbolName); localName = factory.createIdentifier(localText); context.approximateLength += localText.length; } serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment))); } if (length(mergedMembers)) { const containingFile = getSourceFileOfNode(context.enclosingDeclaration); const localName = getInternalSymbolName(symbol, symbolName); const nsBody = factory.createModuleBlock([factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports(mapDefined(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => { const name = unescapeLeadingUnderscores(s.escapedName); const localName = getInternalSymbolName(s, name); const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s); if (containingFile && (aliasDecl ? containingFile !== getSourceFileOfNode(aliasDecl) : !some(s.declarations, d => getSourceFileOfNode(d) === containingFile))) { context.tracker?.reportNonlocalAugmentation?.(containingFile, symbol, s); return undefined; } const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); includePrivateSymbol(target || s); const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName; return factory.createExportSpecifier(/*isTypeOnly*/ false, name === targetName ? undefined : targetName, name); })), )]); addResult( factory.createModuleDeclaration( /*modifiers*/ undefined, factory.createIdentifier(localName), nsBody, NodeFlags.Namespace, ), ModifierFlags.None, ); } } function serializeEnum(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { const internalSymbolName = getInternalSymbolName(symbol, symbolName); context.approximateLength += 9 + internalSymbolName.length; // `enum internalName { }` const members: EnumMember[] = []; const memberProps = filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)); let i = 0; for (const p of memberProps) { i++; if (checkTruncationLengthIfExpanding(context) && (i + 2 !length(p.declarations) || some(p.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!)) || expanding ? "local" : "remote"); const localProps = localVsRemoteMap.get("local") || emptyArray; // handle remote props first - we need to make an `import` declaration that points at the module containing each remote // prop in the outermost scope (TODO: a namespace within a namespace would need to be appropriately handled by this) // Example: // import Foo_1 = require("./exporter"); // export namespace ns { // import Foo = Foo_1.Foo; // export { Foo }; // export const c: number; // } // This is needed because in JS, statements like `const x = require("./f")` support both type and value lookup, even if they're // normally just value lookup (so it functions kinda like an alias even when it's not an alias) // _Usually_, we'll simply print the top-level as an alias instead of a `var` in such situations, however is is theoretically // possible to encounter a situation where a type has members from both the current file and other files - in those situations, // emit akin to the above would be needed. // Add a namespace // Create namespace as non-synthetic so it is usable as an enclosing declaration let fakespace = parseNodeFactory.createModuleDeclaration(/*modifiers*/ undefined, localName, factory.createModuleBlock([]), nodeFlags); setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); fakespace.locals = createSymbolTable(props); fakespace.symbol = props[0].parent!; const oldResults = results; results = []; const oldAddingDeclare = addingDeclare; addingDeclare = false; const subcontext = { ...context, enclosingDeclaration: fakespace }; const oldContext = context; context = subcontext; // TODO: implement handling for the localVsRemoteMap.get("remote") - should be difficult to trigger (see comment above), as only interesting cross-file js merges should make this possible visitSymbolTable(createSymbolTable(localProps), suppressNewPrivateContext, /*propertyAsAlias*/ true); context = oldContext; addingDeclare = oldAddingDeclare; const declarations = results; results = oldResults; // replace namespace with synthetic version const defaultReplaced = map(declarations, d => isExportAssignment(d) && !d.isExportEquals && isIdentifier(d.expression) ? factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, d.expression, factory.createIdentifier(InternalSymbolName.Default))]), ) : d); const exportModifierStripped = every(defaultReplaced, d => hasSyntacticModifier(d, ModifierFlags.Export)) ? map(defaultReplaced as Extract[], removeExportModifier) : defaultReplaced; fakespace = factory.updateModuleDeclaration( fakespace, fakespace.modifiers, fakespace.name, factory.createModuleBlock(exportModifierStripped), ); addResult(fakespace, modifierFlags); // namespaces can never be default exported } else if (expanding) { context.approximateLength += 14; // "namespace { }" addResult( factory.createModuleDeclaration( /*modifiers*/ undefined, localName, factory.createModuleBlock([]), nodeFlags, ), modifierFlags, ); } } function isNamespaceMember(p: Symbol) { return !!(p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) || !(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && isStatic(p.valueDeclaration) && isClassLike(p.valueDeclaration.parent)); } function sanitizeJSDocImplements(clauses: readonly ExpressionWithTypeArguments[]): ExpressionWithTypeArguments[] | undefined { const result = mapDefined(clauses, e => { const oldEnclosing = context.enclosingDeclaration; context.enclosingDeclaration = e; let expr = e.expression; if (isEntityNameExpression(expr)) { if (isIdentifier(expr) && idText(expr) === "") { return cleanup(/*result*/ undefined); // Empty heritage clause, should be an error, but prefer emitting no heritage clauses to reemitting the empty one } let introducesError: boolean; ({ introducesError, node: expr } = trackExistingEntityName(expr, context)); if (introducesError) { return cleanup(/*result*/ undefined); } } return cleanup(factory.createExpressionWithTypeArguments( expr, map(e.typeArguments, a => syntacticNodeBuilder.tryReuseExistingTypeNode(context, a) || typeToTypeNodeHelper(getTypeFromTypeNode(context, a), context)), )); function cleanup(result: T): T { context.enclosingDeclaration = oldEnclosing; return result; } }); if (result.length === clauses.length) { return result; } return undefined; } function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { context.approximateLength += 9 + localName.length; // `class localName { }` const originalDecl = symbol.declarations?.find(isClassLike); const oldEnclosing = context.enclosingDeclaration; context.enclosingDeclaration = originalDecl || oldEnclosing; const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); forEach(localParams, p => context.approximateLength += symbolName(p.symbol).length); const classType = getTypeWithThisArgument(getDeclaredTypeOfClassOrInterface(symbol)) as InterfaceType; const baseTypes = getBaseTypes(classType); const originalImplements = originalDecl && getEffectiveImplementsTypeNodes(originalDecl); const implementsExpressions = originalImplements && sanitizeJSDocImplements(originalImplements) || mapDefined(getImplementsTypes(classType), serializeImplementedType); const staticType = getTypeOfSymbol(symbol); const isClass = !!staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration); const staticBaseType = isClass ? getBaseConstructorTypeOfClass(staticType as InterfaceType) : anyType; context.approximateLength += (length(baseTypes) ? 8 : 0) + (length(implementsExpressions) ? 11 : 0); // `extends ` and `implements ` const heritageClauses = [ ...!length(baseTypes) ? [] : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))], ...!length(implementsExpressions) ? [] : [factory.createHeritageClause(SyntaxKind.ImplementsKeyword, implementsExpressions)], ]; const symbolProps = getNonInheritedProperties(classType, baseTypes, getPropertiesOfType(classType)); const publicSymbolProps = filter(symbolProps, s => !isHashPrivate(s)); const hasPrivateIdentifier = some(symbolProps, isHashPrivate); // Boil down all private properties into a single one. const privateProperties = hasPrivateIdentifier ? isExpanding(context) ? serializePropertySymbolsForClassOrInterface(filter(symbolProps, isHashPrivate), /*isClass*/ true, baseTypes[0], /*isStatic*/ false) : [factory.createPropertyDeclaration( /*modifiers*/ undefined, factory.createPrivateIdentifier("#private"), /*questionOrExclamationToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined, )] : emptyArray; if (hasPrivateIdentifier && !isExpanding(context)) { context.approximateLength += 9; // `#private;` } const publicProperties = serializePropertySymbolsForClassOrInterface(publicSymbolProps, /*isClass*/ true, baseTypes[0], /*isStatic*/ false); // Consider static members empty if symbol also has function or module meaning - function namespacey emit will handle statics const staticMembers = serializePropertySymbolsForClassOrInterface( filter(getPropertiesOfType(staticType), p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype" && !isNamespaceMember(p)), /*isClass*/ true, staticBaseType, /*isStatic*/ true, ); // When we encounter an `X.prototype.y` assignment in a JS file, we bind `X` as a class regardless as to whether // the value is ever initialized with a class or function-like value. For cases where `X` could never be // created via `new`, we will inject a `private constructor()` declaration to indicate it is not createable. const isNonConstructableClassLikeInJsFile = !isClass && !!symbol.valueDeclaration && isInJSFile(symbol.valueDeclaration) && !some(getSignaturesOfType(staticType, SignatureKind.Construct)); if (isNonConstructableClassLikeInJsFile) context.approximateLength += 21; // `private constructor()` const constructors = isNonConstructableClassLikeInJsFile ? [factory.createConstructorDeclaration(factory.createModifiersFromModifierFlags(ModifierFlags.Private), [], /*body*/ undefined)] : serializeSignatures(SignatureKind.Construct, staticType, staticBaseType, SyntaxKind.Constructor) as ConstructorDeclaration[]; const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]); context.enclosingDeclaration = oldEnclosing; addResult( setTextRange( context, factory.createClassDeclaration( /*modifiers*/ undefined, localName, typeParamDecls, heritageClauses, [...indexSignatures, ...staticMembers, ...constructors, ...publicProperties, ...privateProperties], ), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0], ), modifierFlags, ); } function getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { return firstDefined(declarations, d => { if (isImportSpecifier(d) || isExportSpecifier(d)) { return moduleExportNameTextUnescaped(d.propertyName || d.name); } if (isBinaryExpression(d) || isExportAssignment(d)) { const expression = isExportAssignment(d) ? d.expression : d.right; if (isPropertyAccessExpression(expression)) { return idText(expression.name); } } if (isAliasSymbolDeclaration(d)) { // This is... heuristic, at best. But it's probably better than always printing the name of the shorthand ambient module. const name = getNameOfDeclaration(d); if (name && isIdentifier(name)) { return idText(name); } } return undefined; }); } function serializeAsAlias(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { // synthesize an alias, eg `export { symbolName as Name }` // need to mark the alias `symbol` points at // as something we need to serialize as a private declaration as well const node = getDeclarationOfAliasSymbol(symbol); if (!node) return Debug.fail(); const target = getMergedSymbol(getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true)); if (!target) { return; } // If `target` refers to a shorthand module symbol, the name we're trying to pull out isn;t recoverable from the target symbol // In such a scenario, we must fall back to looking for an alias declaration on `symbol` and pulling the target name from that let verbatimTargetName = isShorthandAmbientModuleSymbol(target) && getSomeTargetNameFromDeclarations(symbol.declarations) || unescapeLeadingUnderscores(target.escapedName); if (verbatimTargetName === InternalSymbolName.ExportEquals && allowSyntheticDefaultImports) { // target refers to an `export=` symbol that was hoisted into a synthetic default - rename here to match verbatimTargetName = InternalSymbolName.Default; } const targetName = getInternalSymbolName(target, verbatimTargetName); includePrivateSymbol(target); // the target may be within the same scope - attempt to serialize it first switch (node.kind) { case SyntaxKind.BindingElement: if (node.parent?.parent?.kind === SyntaxKind.VariableDeclaration) { // const { SomeClass } = require('./lib'); const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // './lib' const { propertyName } = node as BindingElement; const propertyNameText = propertyName && isIdentifier(propertyName) ? idText(propertyName) : undefined; context.approximateLength += 24 + localName.length + specifier.length + (propertyNameText?.length ?? 0); // `import { propertyName as name } from "specifier";` addResult( factory.createImportDeclaration( /*modifiers*/ undefined, factory.createImportClause( /*phaseModifier*/ undefined, /*name*/ undefined, factory.createNamedImports([factory.createImportSpecifier( /*isTypeOnly*/ false, propertyNameText ? factory.createIdentifier(propertyNameText) : undefined, factory.createIdentifier(localName), )]), ), factory.createStringLiteral(specifier), /*attributes*/ undefined, ), ModifierFlags.None, ); break; } // We don't know how to serialize this (nested?) binding element Debug.failBadSyntaxKind(node.parent?.parent || node, "Unhandled binding element grandparent kind in declaration serialization"); break; case SyntaxKind.ShorthandPropertyAssignment: if (node.parent?.parent?.kind === SyntaxKind.BinaryExpression) { // module.exports = { SomeClass } serializeExportSpecifier( unescapeLeadingUnderscores(symbol.escapedName), targetName, ); } break; case SyntaxKind.VariableDeclaration: // commonjs require: const x = require('y') if (isPropertyAccessExpression((node as VariableDeclaration).initializer!)) { // const x = require('y').z const initializer = (node as VariableDeclaration).initializer! as PropertyAccessExpression; // require('y').z const uniqueName = factory.createUniqueName(localName); // _x const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // 'y' // import _x = require('y'); context.approximateLength += 22 + specifier.length + idText(uniqueName).length; // `import uniqueName = require("specifier");` addResult( factory.createImportEqualsDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, uniqueName, factory.createExternalModuleReference(factory.createStringLiteral(specifier)), ), ModifierFlags.None, ); // import x = _x.z context.approximateLength += 12 + localName.length + idText(uniqueName).length + idText(initializer.name).length; // `import localName = uniqueName.initializerName;` addResult( factory.createImportEqualsDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createIdentifier(localName), factory.createQualifiedName(uniqueName, initializer.name as Identifier), ), modifierFlags, ); break; } // else fall through and treat commonjs require just like import= case SyntaxKind.ImportEqualsDeclaration: // This _specifically_ only exists to handle json declarations - where we make aliases, but since // we emit no declarations for the json document, must not refer to it in the declarations if (target.escapedName === InternalSymbolName.ExportEquals && some(target.declarations, d => isSourceFile(d) && isJsonSourceFile(d))) { serializeMaybeAliasAssignment(symbol); break; } // Could be a local `import localName = ns.member` or // an external `import localName = require("whatever")` const isLocalImport = !(target.flags & SymbolFlags.ValueModule) && !isVariableDeclaration(node); context.approximateLength += 11 + localName.length + unescapeLeadingUnderscores(target.escapedName).length; // `import localName = target;` addResult( factory.createImportEqualsDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createIdentifier(localName), isLocalImport ? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) : factory.createExternalModuleReference(factory.createStringLiteral(getSpecifierForModuleSymbol(target, context))), ), isLocalImport ? modifierFlags : ModifierFlags.None, ); break; case SyntaxKind.NamespaceExportDeclaration: // export as namespace foo // TODO: Not part of a file's local or export symbol tables // Is bound into file.symbol.globalExports instead, which we don't currently traverse addResult(factory.createNamespaceExportDeclaration(idText((node as NamespaceExportDeclaration).name)), ModifierFlags.None); break; case SyntaxKind.ImportClause: { const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportClause).parent.moduleSpecifier; const attributes = isImportDeclaration(node.parent) ? node.parent.attributes : undefined; const isTypeOnly = isJSDocImportTag((node as ImportClause).parent); context.approximateLength += 14 + localName.length + 3 + (isTypeOnly ? 4 : 0); // `import localName from specifier;`, approximate specifier addResult( factory.createImportDeclaration( /*modifiers*/ undefined, factory.createImportClause( /* phaseModifier */ isTypeOnly ? SyntaxKind.TypeKeyword : undefined, factory.createIdentifier(localName), /*namedBindings*/ undefined, ), specifier, attributes, ), ModifierFlags.None, ); break; } case SyntaxKind.NamespaceImport: { const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as NamespaceImport).parent.parent.moduleSpecifier; const isTypeOnly = isJSDocImportTag((node as NamespaceImport).parent.parent); context.approximateLength += 19 + localName.length + 3 + (isTypeOnly ? 4 : 0); // `import * as localName from specifier;`, approximate specifier addResult( factory.createImportDeclaration( /*modifiers*/ undefined, factory.createImportClause( /* phaseModifier */ isTypeOnly ? SyntaxKind.TypeKeyword : undefined, /*name*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName)), ), specifier, (node as ImportClause).parent.attributes, ), ModifierFlags.None, ); break; } case SyntaxKind.NamespaceExport: context.approximateLength += 19 + localName.length + 3; // `export * as localName from specifier;`, approximate specifier addResult( factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamespaceExport(factory.createIdentifier(localName)), factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)), ), ModifierFlags.None, ); break; case SyntaxKind.ImportSpecifier: { const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportSpecifier).parent.parent.parent.moduleSpecifier; const isTypeOnly = isJSDocImportTag((node as ImportSpecifier).parent.parent.parent); context.approximateLength += 19 + localName.length + 3 + (isTypeOnly ? 4 : 0); // `import { localName } from specifier;`, approximate specifier addResult( factory.createImportDeclaration( /*modifiers*/ undefined, factory.createImportClause( /* phaseModifier */ isTypeOnly ? SyntaxKind.TypeKeyword : undefined, /*name*/ undefined, factory.createNamedImports([ factory.createImportSpecifier( /*isTypeOnly*/ false, localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, factory.createIdentifier(localName), ), ]), ), specifier, (node as ImportSpecifier).parent.parent.parent.attributes, ), ModifierFlags.None, ); break; } case SyntaxKind.ExportSpecifier: // does not use localName because the symbol name in this case refers to the name in the exports table, // which we must exactly preserve const specifier = (node.parent.parent as ExportDeclaration).moduleSpecifier; if (specifier) { const propertyName = (node as ExportSpecifier).propertyName; if (propertyName && moduleExportNameIsDefault(propertyName)) { verbatimTargetName = InternalSymbolName.Default; } } // targetName is only used when the target is local, as otherwise the target is an alias that points at // another file serializeExportSpecifier( unescapeLeadingUnderscores(symbol.escapedName), specifier ? verbatimTargetName : targetName, specifier && isStringLiteralLike(specifier) ? factory.createStringLiteral(specifier.text) : undefined, ); break; case SyntaxKind.ExportAssignment: serializeMaybeAliasAssignment(symbol); break; case SyntaxKind.BinaryExpression: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: // Could be best encoded as though an export specifier or as though an export assignment // If name is default or export=, do an export assignment // Otherwise do an export specifier if (symbol.escapedName === InternalSymbolName.Default || symbol.escapedName === InternalSymbolName.ExportEquals) { serializeMaybeAliasAssignment(symbol); } else { serializeExportSpecifier(localName, targetName); } break; default: return Debug.failBadSyntaxKind(node, "Unhandled alias declaration kind in symbol serializer!"); } } function serializeExportSpecifier(localName: string, targetName: string, specifier?: Expression) { context.approximateLength += 16 + localName.length + (localName !== targetName ? targetName.length : 0); // `export { targetName as localName };` addResult( factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, localName !== targetName ? targetName : undefined, localName)]), specifier, ), ModifierFlags.None, ); } /** * Returns `true` if an export assignment or declaration was produced for the symbol */ function serializeMaybeAliasAssignment(symbol: Symbol): boolean { if (symbol.flags & SymbolFlags.Prototype) { return false; } const name = unescapeLeadingUnderscores(symbol.escapedName); const isExportEquals = name === InternalSymbolName.ExportEquals; const isDefault = name === InternalSymbolName.Default; const isExportAssignmentCompatibleSymbolName = isExportEquals || isDefault; // synthesize export = ref // ref should refer to either be a locally scoped symbol which we need to emit, or // a reference to another namespace/module which we may need to emit an `import` statement for const aliasDecl = symbol.declarations && getDeclarationOfAliasSymbol(symbol); // serialize what the alias points to, preserve the declaration's initializer const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); // If the target resolves and resolves to a thing defined in this file, emit as an alias, otherwise emit as a const if (target && length(target.declarations) && some(target.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(enclosingDeclaration))) { // In case `target` refers to a namespace member, look at the declaration and serialize the leftmost symbol in it // eg, `namespace A { export class B {} }; exports = A.B;` // Technically, this is all that's required in the case where the assignment is an entity name expression const expr = aliasDecl && ((isExportAssignment(aliasDecl) || isBinaryExpression(aliasDecl)) ? getExportAssignmentExpression(aliasDecl) : getPropertyAssignmentAliasLikeExpression(aliasDecl as ShorthandPropertyAssignment | PropertyAssignment | PropertyAccessExpression)); const first = expr && isEntityNameExpression(expr) ? getFirstNonModuleExportsIdentifier(expr) : undefined; const referenced = first && resolveEntityName(first, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, enclosingDeclaration); if (referenced || target) { includePrivateSymbol(referenced || target); } // We disable the context's symbol tracker for the duration of this name serialization // as, by virtue of being here, the name is required to print something, and we don't want to // issue a visibility error on it. Only anonymous classes that an alias points at _would_ issue // a visibility error here (as they're not visible within any scope), but we want to hoist them // into the containing scope anyway, so we want to skip the visibility checks. const prevDisableTrackSymbol = context.tracker.disableTrackSymbol; context.tracker.disableTrackSymbol = true; if (isExportAssignmentCompatibleSymbolName) { context.approximateLength += 10; // `export = ;` results.push(factory.createExportAssignment( /*modifiers*/ undefined, isExportEquals, symbolToExpression(target, context, SymbolFlags.All), )); } else { if (first === expr && first) { // serialize as `export {target as name}` serializeExportSpecifier(name, idText(first)); } else if (expr && isClassExpression(expr)) { serializeExportSpecifier(name, getInternalSymbolName(target, symbolName(target))); } else { // serialize as `import _Ref = t.arg.et; export { _Ref as name }` const varName = getUnusedName(name, symbol); context.approximateLength += varName.length + 10; // `import name = ;` addResult( factory.createImportEqualsDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createIdentifier(varName), symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false), ), ModifierFlags.None, ); serializeExportSpecifier(name, varName); } } context.tracker.disableTrackSymbol = prevDisableTrackSymbol; return true; } else { // serialize as an anonymous property declaration const varName = getUnusedName(name, symbol); // We have to use `getWidenedType` here since the object within a json file is unwidened within the file // (Unwidened types can only exist in expression contexts and should never be serialized) const typeToSerialize = getWidenedType(getTypeOfSymbol(getMergedSymbol(symbol))); if (isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize, symbol)) { // If there are no index signatures and `typeToSerialize` is an object type, emit as a namespace instead of a const serializeAsFunctionNamespaceMerge(typeToSerialize, symbol, varName, isExportAssignmentCompatibleSymbolName ? ModifierFlags.None : ModifierFlags.Export); } else { const flags = context.enclosingDeclaration?.kind === SyntaxKind.ModuleDeclaration && (!(symbol.flags & SymbolFlags.Accessor) || symbol.flags & SymbolFlags.SetAccessor) ? NodeFlags.Let : NodeFlags.Const; context.approximateLength += varName.length + 5; // `var name: ;` const statement = factory.createVariableStatement( /*modifiers*/ undefined, factory.createVariableDeclarationList([ factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, /*declaration*/ undefined, typeToSerialize, symbol)), ], flags), ); // Inlined JSON types exported with [module.]exports= will already emit an export=, so should use `declare`. // Otherwise, the type itself should be exported. addResult( statement, target && target.flags & SymbolFlags.Property && target.escapedName === InternalSymbolName.ExportEquals ? ModifierFlags.Ambient : name === varName ? ModifierFlags.Export : ModifierFlags.None, ); } if (isExportAssignmentCompatibleSymbolName) { context.approximateLength += varName.length + 10; // `export = name;` results.push(factory.createExportAssignment( /*modifiers*/ undefined, isExportEquals, factory.createIdentifier(varName), )); return true; } else if (name !== varName) { serializeExportSpecifier(name, varName); return true; } return false; } } function isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize: Type, hostSymbol: Symbol) { // Only object types which are not constructable, or indexable, whose members all come from the // context source file, and whose property names are all valid identifiers and not late-bound, _and_ // whose input is not type annotated (if the input symbol has an annotation we can reuse, we should prefer it) const ctxSrc = getSourceFileOfNode(context.enclosingDeclaration); return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) && !some(typeToSerialize.symbol?.declarations, isTypeNode) && // If the type comes straight from a type node, we shouldn't try to break it up !length(getIndexInfosOfType(typeToSerialize)) && !isClassInstanceSide(typeToSerialize) && // While a class instance is potentially representable as a NS, prefer printing a reference to the instance type and serializing the class !!(length(filter(getPropertiesOfType(typeToSerialize), isNamespaceMember)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) && !length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK !getDeclarationWithTypeAnnotation(hostSymbol, enclosingDeclaration) && !(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && !some(getPropertiesOfType(typeToSerialize), p => isLateBoundName(p.escapedName)) && !some(getPropertiesOfType(typeToSerialize), p => some(p.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && every(getPropertiesOfType(typeToSerialize), p => { if (!isIdentifierText(symbolName(p), languageVersion)) { return false; } if (!(p.flags & SymbolFlags.Accessor)) { return true; } return getNonMissingTypeOfSymbol(p) === getWriteTypeOfSymbol(p); }); } function makeSerializePropertySymbol( createProperty: ( modifiers: readonly Modifier[] | undefined, name: string | PropertyName, questionOrExclamationToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined, ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: true, ): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => T | AccessorDeclaration | (T | AccessorDeclaration)[]; function makeSerializePropertySymbol( createProperty: ( modifiers: readonly Modifier[] | undefined, name: string | PropertyName, questionOrExclamationToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined, ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: false, ): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => T | T[]; function makeSerializePropertySymbol( createProperty: ( modifiers: readonly Modifier[] | undefined, name: string | PropertyName, questionOrExclamationToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined, ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: boolean, ): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => T | AccessorDeclaration | (T | AccessorDeclaration)[] { return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined): T | AccessorDeclaration | (T | AccessorDeclaration)[] { const modifierFlags = getDeclarationModifierFlagsFromSymbol(p); const omitType = !!(modifierFlags & ModifierFlags.Private) && !isExpanding(context); if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) { // Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols // need to be merged namespace members return []; } if ( p.flags & SymbolFlags.Prototype || p.escapedName === "constructor" || (baseType && getPropertyOfType(baseType, p.escapedName) && isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p) && (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional) && isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!)) ) { return []; } const flag = (modifierFlags & ~ModifierFlags.Async) | (isStatic ? ModifierFlags.Static : 0); const name = getPropertyNameNodeForSymbol(p, context); const firstPropertyLikeDecl = p.declarations?.find(or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); if (p.flags & SymbolFlags.Accessor && useAccessors) { const result: AccessorDeclaration[] = []; if (p.flags & SymbolFlags.SetAccessor) { const setter = p.declarations && forEach(p.declarations, d => { if (d.kind === SyntaxKind.SetAccessor) { return d as SetAccessorDeclaration; } if (isCallExpression(d) && isBindableObjectDefinePropertyCall(d)) { return forEach(d.arguments[2].properties, propDecl => { const id = getNameOfDeclaration(propDecl); if (!!id && isIdentifier(id) && idText(id) === "set") { return propDecl; } }); } }); Debug.assert(!!setter); const paramSymbol = isFunctionLikeDeclaration(setter) ? getSignatureFromDeclaration(setter).parameters[0] : undefined; const setterDeclaration = p.declarations?.find(isSetAccessor); context.approximateLength += modifiersLength(flag) + 7 + (paramSymbol ? symbolName(paramSymbol).length : 5) + (omitType ? 0 : 2); // `modifiers set name(param);`, approximate name result.push(setTextRange( context, factory.createSetAccessorDeclaration( factory.createModifiersFromModifierFlags(flag), name, [factory.createParameterDeclaration( /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, paramSymbol ? parameterToParameterDeclarationName(paramSymbol, getEffectiveParameterDeclaration(paramSymbol), context) : "value", /*questionToken*/ undefined, omitType ? undefined : serializeTypeForDeclaration(context, setterDeclaration, getWriteTypeOfSymbol(p), p), )], /*body*/ undefined, ), setterDeclaration ?? firstPropertyLikeDecl, )); } if (p.flags & SymbolFlags.GetAccessor) { const getterDeclaration = p.declarations?.find(isGetAccessor); context.approximateLength += modifiersLength(flag) + 8 + (omitType ? 0 : 2); // `modifiers get name(): ;`, approximate name result.push(setTextRange( context, factory.createGetAccessorDeclaration( factory.createModifiersFromModifierFlags(flag), name, [], omitType ? undefined : serializeTypeForDeclaration(context, getterDeclaration, getTypeOfSymbol(p), p), /*body*/ undefined, ), getterDeclaration ?? firstPropertyLikeDecl, )); } return result; } // This is an else/if as accessors and properties can't merge in TS, but might in JS // If this happens, we assume the accessor takes priority, as it imposes more constraints else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable | SymbolFlags.Accessor)) { const modifierFlags = (isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag; context.approximateLength += 2 + (omitType ? 0 : 2) + modifiersLength(modifierFlags); // `modifiers name: ;`, approximate name return setTextRange( context, createProperty( factory.createModifiersFromModifierFlags(modifierFlags), name, p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, omitType ? undefined : serializeTypeForDeclaration(context, p.declarations?.find(isSetAccessorDeclaration), getWriteTypeOfSymbol(p), p), // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 // interface members can't have initializers, however class members _can_ /*initializer*/ undefined, ), p.declarations?.find(or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl, ); } if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { const type = getTypeOfSymbol(p); const signatures = getSignaturesOfType(type, SignatureKind.Call); if (omitType) { const modifierFlags = (isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag; context.approximateLength += 1 + modifiersLength(modifierFlags); // `modifiers name;`, approximate name return setTextRange( context, createProperty( factory.createModifiersFromModifierFlags(modifierFlags), name, p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, /*type*/ undefined, /*initializer*/ undefined, ), p.declarations?.find(isFunctionLikeDeclaration) || signatures[0] && signatures[0].declaration || p.declarations && p.declarations[0], ); } const results = []; for (const sig of signatures) { context.approximateLength += 1; // ; // Each overload becomes a separate method declaration, in order const decl = signatureToSignatureDeclarationHelper( sig, methodKind, context, { name, questionToken: p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, modifiers: flag ? factory.createModifiersFromModifierFlags(flag) : undefined, }, ); const location = sig.declaration && isPrototypePropertyAssignment(sig.declaration.parent) ? sig.declaration.parent : sig.declaration; results.push(setTextRange(context, decl, location)); } return results as unknown as T[]; } // The `Constructor`'s symbol isn't in the class's properties lists, obviously, since it's a signature on the static return Debug.fail(`Unhandled class member kind! ${(p as any).__debugFlags || p.flags}`); }; } function modifiersLength(flags: ModifierFlags): number { let result = 0; // Include the trailing space after the modifier keyword. if (flags & ModifierFlags.Export) result += 7; if (flags & ModifierFlags.Ambient) result += 8; if (flags & ModifierFlags.Default) result += 8; if (flags & ModifierFlags.Const) result += 6; if (flags & ModifierFlags.Public) result += 7; if (flags & ModifierFlags.Private) result += 8; if (flags & ModifierFlags.Protected) result += 10; if (flags & ModifierFlags.Abstract) result += 9; if (flags & ModifierFlags.Static) result += 7; if (flags & ModifierFlags.Override) result += 9; if (flags & ModifierFlags.Readonly) result += 9; if (flags & ModifierFlags.Accessor) result += 9; if (flags & ModifierFlags.Async) result += 6; if (flags & ModifierFlags.In) result += 3; if (flags & ModifierFlags.Out) result += 4; return result; } function serializePropertySymbolForInterface(p: Symbol, baseType: Type | undefined) { return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType); } function serializeSignatures(kind: SignatureKind, input: Type, baseType: Type | undefined, outputKind: SignatureDeclaration["kind"]) { const signatures = getSignaturesOfType(input, kind); if (kind === SignatureKind.Construct) { if (!baseType && every(signatures, s => length(s.parameters) === 0)) { return []; // No base type, every constructor is empty - elide the extraneous `constructor()` } if (baseType) { // If there is a base type, if every signature in the class is identical to a signature in the baseType, elide all the declarations const baseSigs = getSignaturesOfType(baseType, SignatureKind.Construct); if (!length(baseSigs) && every(signatures, s => length(s.parameters) === 0)) { return []; // Base had no explicit signatures, if all our signatures are also implicit, return an empty list } if (baseSigs.length === signatures.length) { let failed = false; for (let i = 0; i typeToTypeNodeHelper(t, context)); reference = symbolToExpression((t as TypeReference).target.symbol, context, SymbolFlags.Type); } else if (t.symbol && isSymbolAccessibleByFlags(t.symbol, enclosingDeclaration, flags)) { reference = symbolToExpression(t.symbol, context, SymbolFlags.Type); } if (reference) { return factory.createExpressionWithTypeArguments(reference, typeArgs); } } function serializeImplementedType(t: Type) { const ref = trySerializeAsTypeReference(t, SymbolFlags.Type); if (ref) { return ref; } if (t.symbol) { return factory.createExpressionWithTypeArguments(symbolToExpression(t.symbol, context, SymbolFlags.Type), /*typeArguments*/ undefined); } } function getUnusedName(input: string, symbol?: Symbol): string { const id = symbol ? getSymbolId(symbol) : undefined; if (id) { if (context.remappedSymbolNames!.has(id)) { return context.remappedSymbolNames!.get(id)!; } } if (symbol) { input = getNameCandidateWorker(symbol, input); } let i = 0; const original = input; while (context.usedSymbolNames?.has(input)) { i++; input = `${original}_${i}`; } context.usedSymbolNames?.add(input); if (id) { context.remappedSymbolNames!.set(id, input); } return input; } function getNameCandidateWorker(symbol: Symbol, localName: string) { if (localName === InternalSymbolName.Default || localName === InternalSymbolName.Class || localName === InternalSymbolName.Function) { const restoreFlags = saveRestoreFlags(context); context.flags |= NodeBuilderFlags.InInitialEntityName; const nameCandidate = getNameOfSymbolAsWritten(symbol, context); restoreFlags(); localName = nameCandidate.length > 0 && isSingleOrDoubleQuote(nameCandidate.charCodeAt(0)) ? stripQuotes(nameCandidate) : nameCandidate; } if (localName === InternalSymbolName.Default) { localName = "_default"; } else if (localName === InternalSymbolName.ExportEquals) { localName = "_exports"; } localName = isIdentifierText(localName, languageVersion) && !isStringANonContextualKeyword(localName) ? localName : "_" + localName.replace(/[^a-z0-9]/gi, "_"); return localName; } function getInternalSymbolName(symbol: Symbol, localName: string) { const id = getSymbolId(symbol); if (context.remappedSymbolNames!.has(id)) { return context.remappedSymbolNames!.get(id)!; } localName = getNameCandidateWorker(symbol, localName); // The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up context.remappedSymbolNames!.set(id, localName); return localName; } } function isExpanding(context: NodeBuilderContext) { return context.maxExpansionDepth !== -1; } function isHashPrivate(s: Symbol): boolean { // `valueDeclaration` could be undefined if inherited from // a union/intersection base type, but inherited properties // don't matter here. return !!s.valueDeclaration && isNamedDeclaration(s.valueDeclaration) && isPrivateIdentifier(s.valueDeclaration.name); } function getClonedHashPrivateName(s: Symbol): PrivateIdentifier | undefined { if (s.valueDeclaration && isNamedDeclaration(s.valueDeclaration) && isPrivateIdentifier(s.valueDeclaration.name)) { return factory.cloneNode(s.valueDeclaration.name); } return undefined; } } /** Returns true if a type is declared in a lib file. */ // Don't expand types like `Array` or `Promise`, instead treating them as opaque. function isLibType(type: Type): boolean { const symbol = (getObjectFlags(type) & ObjectFlags.Reference) !== 0 ? (type as TypeReference).target.symbol : type.symbol; return isTupleType(type) || !!(symbol?.declarations?.some(decl => host.isSourceFileDefaultLibrary(getSourceFileOfNode(decl)))); } function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); function typePredicateToStringWorker(writer: EmitTextWriter) { const nodeBuilderFlags = toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName; const predicate = nodeBuilder.typePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, nodeBuilderFlags)!; // TODO: GH#18217 const printer = createPrinterWithRemoveComments(); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer); return writer; } } function formatUnionTypes(types: readonly Type[], expandingEnum: boolean): Type[] { const result: Type[] = []; let flags = 0 as TypeFlags; for (let i = 0; i getNameOfDeclaration(d) ? d : undefined); // Try using a declaration with a name, first const name = declaration && getNameOfDeclaration(declaration); if (declaration && name) { if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { return symbolName(symbol); } if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late)) { const nameType = getSymbolLinks(symbol).nameType; if (nameType && nameType.flags & TypeFlags.StringOrNumberLiteral) { // Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name const result = getNameOfSymbolFromNameType(symbol, context); if (result !== undefined) { return result; } } } return declarationNameToString(name); } if (!declaration) { declaration = symbol.declarations[0]; // Declaration may be nameless, but we'll try anyway } if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { return declarationNameToString((declaration.parent as VariableDeclaration).name); } switch (declaration.kind) { case SyntaxKind.ClassExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: if (context && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { context.encounteredError = true; } return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)"; } } const name = getNameOfSymbolFromNameType(symbol, context); return name !== undefined ? name : symbolName(symbol); } function isDeclarationVisible(node: Node): boolean { if (node) { const links = getNodeLinks(node); if (links.isVisible === undefined) { links.isVisible = !!determineIfDeclarationIsVisible(); } return links.isVisible; } return false; function determineIfDeclarationIsVisible() { switch (node.kind) { case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocEnumTag: // Top-level jsdoc type aliases are considered exported // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent)); case SyntaxKind.BindingElement: return isDeclarationVisible(node.parent.parent); case SyntaxKind.VariableDeclaration: if ( isBindingPattern((node as VariableDeclaration).name) && !((node as VariableDeclaration).name as BindingPattern).elements.length ) { // If the binding pattern is empty, this variable declaration is not visible return false; } // falls through case SyntaxKind.ModuleDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.FunctionDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.ImportEqualsDeclaration: // external module augmentation is always visible if (isExternalModuleAugmentation(node)) { return true; } const parent = getDeclarationContainer(node); // If the node is not exported or it is not ambient module element (except import declaration) if ( !(getCombinedModifierFlagsCached(node as Declaration) & ModifierFlags.Export) && !(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && parent.flags & NodeFlags.Ambient) ) { return isGlobalSourceFile(parent); } // Exported members/ambient module elements (exception import declaration) are visible if parent is visible return isDeclarationVisible(parent); case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: if (hasEffectiveModifier(node, ModifierFlags.Private | ModifierFlags.Protected)) { // Private/protected properties/methods are not visible return false; } // Public properties/methods are visible if its parents are visible, so: // falls through case SyntaxKind.Constructor: case SyntaxKind.ConstructSignature: case SyntaxKind.CallSignature: case SyntaxKind.IndexSignature: case SyntaxKind.Parameter: case SyntaxKind.ModuleBlock: case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.TypeLiteral: case SyntaxKind.TypeReference: case SyntaxKind.ArrayType: case SyntaxKind.TupleType: case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: case SyntaxKind.ParenthesizedType: case SyntaxKind.NamedTupleMember: return isDeclarationVisible(node.parent); // Default binding, import specifier and namespace import is visible // only on demand so by default it is not visible case SyntaxKind.ImportClause: case SyntaxKind.NamespaceImport: case SyntaxKind.ImportSpecifier: return false; // Type parameters are always visible case SyntaxKind.TypeParameter: // Source file and namespace export are always visible // falls through case SyntaxKind.SourceFile: case SyntaxKind.NamespaceExportDeclaration: return true; // Export assignments do not create name bindings outside the module case SyntaxKind.ExportAssignment: return false; default: return false; } } } function collectLinkedAliases(node: ModuleExportName, setVisibility?: boolean): Node[] | undefined { let exportSymbol: Symbol | undefined; if (node.kind !== SyntaxKind.StringLiteral && node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { exportSymbol = resolveName(node, node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); } else if (node.parent.kind === SyntaxKind.ExportSpecifier) { exportSymbol = getTargetOfExportSpecifier(node.parent as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); } let result: Node[] | undefined; let visited: Set | undefined; if (exportSymbol) { visited = new Set(); visited.add(getSymbolId(exportSymbol)); buildVisibleNodeList(exportSymbol.declarations); } return result; function buildVisibleNodeList(declarations: Declaration[] | undefined) { forEach(declarations, declaration => { const resultNode = getAnyImportSyntax(declaration) || declaration; if (setVisibility) { getNodeLinks(declaration).isVisible = true; } else { result = result || []; pushIfUnique(result, resultNode); } if (isInternalModuleImportEqualsDeclaration(declaration)) { // Add the referenced top container visible const internalModuleReference = declaration.moduleReference as Identifier | QualifiedName; const firstIdentifier = getFirstIdentifier(internalModuleReference); const importSymbol = resolveName(declaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); if (importSymbol && visited) { if (tryAddToSet(visited, getSymbolId(importSymbol))) { buildVisibleNodeList(importSymbol.declarations); } } } }); } } /** * Push an entry on the type resolution stack. If an entry with the given target and the given property name * is already on the stack, and no entries in between already have a type, then a circularity has occurred. * In this case, the result values of the existing entry and all entries pushed after it are changed to false, * and the value false is returned. Otherwise, the new entry is just pushed onto the stack, and true is returned. * In order to see if the same query has already been done before, the target object and the propertyName both * must match the one passed in. * * @param target The symbol, type, or signature whose type is being queried * @param propertyName The property name that should be used to query the target for its type */ function pushTypeResolution(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { const resolutionCycleStartIndex = findResolutionCycleStartIndex(target, propertyName); if (resolutionCycleStartIndex >= 0) { // A cycle was found const { length } = resolutionTargets; for (let i = resolutionCycleStartIndex; i = resolutionStart; i--) { if (resolutionTargetHasProperty(resolutionTargets[i], resolutionPropertyNames[i])) { return -1; } if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { return i; } } return -1; } function resolutionTargetHasProperty(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { switch (propertyName) { case TypeSystemPropertyName.Type: return !!getSymbolLinks(target as Symbol).type; case TypeSystemPropertyName.DeclaredType: return !!getSymbolLinks(target as Symbol).declaredType; case TypeSystemPropertyName.ResolvedBaseConstructorType: return !!(target as InterfaceType).resolvedBaseConstructorType; case TypeSystemPropertyName.ResolvedReturnType: return !!(target as Signature).resolvedReturnType; case TypeSystemPropertyName.ImmediateBaseConstraint: return !!(target as Type).immediateBaseConstraint; case TypeSystemPropertyName.ResolvedTypeArguments: return !!(target as TypeReference).resolvedTypeArguments; case TypeSystemPropertyName.ResolvedBaseTypes: return !!(target as InterfaceType).baseTypesResolved; case TypeSystemPropertyName.WriteType: return !!getSymbolLinks(target as Symbol).writeType; case TypeSystemPropertyName.ParameterInitializerContainsUndefined: return getNodeLinks(target as ParameterDeclaration).parameterInitializerContainsUndefined !== undefined; } return Debug.assertNever(propertyName); } /** * Pop an entry from the type resolution stack and return its associated result value. The result value will * be true if no circularities were detected, or false if a circularity was found. */ function popTypeResolution(): boolean { resolutionTargets.pop(); resolutionPropertyNames.pop(); return resolutionResults.pop()!; } function getDeclarationContainer(node: Node): Node { return findAncestor(getRootDeclaration(node), node => { switch (node.kind) { case SyntaxKind.VariableDeclaration: case SyntaxKind.VariableDeclarationList: case SyntaxKind.ImportSpecifier: case SyntaxKind.NamedImports: case SyntaxKind.NamespaceImport: case SyntaxKind.ImportClause: return false; default: return true; } })!.parent; } function getTypeOfPrototypeProperty(prototype: Symbol): Type { // TypeScript 1.0 spec (April 2014): 8.4 // Every class automatically contains a static property member named 'prototype', // the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter. // It is an error to explicitly declare a static property member with the name 'prototype'. const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType; return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, _ => anyType)) : classType; } // Return the type of the given property in the given type, or undefined if no such property exists function getTypeOfPropertyOfType(type: Type, name: __String): Type | undefined { const prop = getPropertyOfType(type, name); return prop ? getTypeOfSymbol(prop) : undefined; } /** * Return the type of the matching property or index signature in the given type, or undefined * if no matching property or index signature exists. Add optionality to index signature types. */ function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { let propType; return getTypeOfPropertyOfType(type, name) || (propType = getApplicableIndexInfoForName(type, name)?.type) && addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); } function isTypeAny(type: Type | undefined) { return type && (type.flags & TypeFlags.Any) !== 0; } function isErrorType(type: Type) { // The only 'any' types that have alias symbols are those manufactured by getTypeFromTypeAliasReference for // a reference to an unresolved symbol. We want those to behave like the errorType. return type === errorType || !!(type.flags & TypeFlags.Any && type.aliasSymbol); } // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been // assigned by contextual typing. function getTypeForBindingElementParent(node: BindingElementGrandparent, checkMode: CheckMode) { if (checkMode !== CheckMode.Normal) { return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode); } const symbol = getSymbolOfDeclaration(node); return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode); } function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type { source = filterType(source, t => !(t.flags & TypeFlags.Nullable)); if (source.flags & TypeFlags.Never) { return emptyObjectType; } if (source.flags & TypeFlags.Union) { return mapType(source, t => getRestType(t, properties, symbol)); } let omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); const spreadableProperties: Symbol[] = []; const unspreadableToRestKeys: Type[] = []; for (const prop of getPropertiesOfType(source)) { const literalTypeFromProperty = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique); if ( !isTypeAssignableTo(literalTypeFromProperty, omitKeyType) && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) && isSpreadableProperty(prop) ) { spreadableProperties.push(prop); } else { unspreadableToRestKeys.push(literalTypeFromProperty); } } if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) { if (unspreadableToRestKeys.length) { // If the type we're spreading from has properties that cannot // be spread into the rest type (e.g. getters, methods), ensure // they are explicitly omitted, as they would in the non-generic case. omitKeyType = getUnionType([omitKeyType, ...unspreadableToRestKeys]); } if (omitKeyType.flags & TypeFlags.Never) { return source; } const omitTypeAlias = getGlobalOmitSymbol(); if (!omitTypeAlias) { return errorType; } return getTypeAliasInstantiation(omitTypeAlias, [source, omitKeyType]); } const members = createSymbolTable(); for (const prop of spreadableProperties) { members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false)); } const result = createAnonymousType(symbol, members, emptyArray, emptyArray, getIndexInfosOfType(source)); result.objectFlags |= ObjectFlags.ObjectRestType; return result; } function isGenericTypeWithUndefinedConstraint(type: Type) { return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); } function getNonUndefinedType(type: Type) { const typeOrConstraint = someType(type, isGenericTypeWithUndefinedConstraint) ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; return getTypeWithFacts(typeOrConstraint, TypeFacts.NEUndefined); } // Determine the control flow type associated with a destructuring declaration or assignment. The following // forms of destructuring are possible: // let { x } = obj; // BindingElement // let [ x ] = obj; // BindingElement // { x } = obj; // ShorthandPropertyAssignment // { x: v } = obj; // PropertyAssignment // [ x ] = obj; // Expression // We construct a synthetic element access expression corresponding to 'obj.x' such that the control // flow analyzer doesn't have to handle all the different syntactic forms. function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: Type) { const reference = getSyntheticElementAccess(node); return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType; } function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined { const parentAccess = getParentElementAccess(node); if (parentAccess && canHaveFlowNode(parentAccess) && parentAccess.flowNode) { const propName = getDestructuringPropertyName(node); if (propName) { const literal = setTextRangeWorker(parseNodeFactory.createStringLiteral(propName), node); const lhsExpr = isLeftHandSideExpression(parentAccess) ? parentAccess : parseNodeFactory.createParenthesizedExpression(parentAccess); const result = setTextRangeWorker(parseNodeFactory.createElementAccessExpression(lhsExpr, literal), node); setParent(literal, result); setParent(result, node); if (lhsExpr !== parentAccess) { setParent(lhsExpr, result); } result.flowNode = parentAccess.flowNode; return result; } } } function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { const ancestor = node.parent.parent; switch (ancestor.kind) { case SyntaxKind.BindingElement: case SyntaxKind.PropertyAssignment: return getSyntheticElementAccess(ancestor as BindingElement | PropertyAssignment); case SyntaxKind.ArrayLiteralExpression: return getSyntheticElementAccess(node.parent as Expression); case SyntaxKind.VariableDeclaration: return (ancestor as VariableDeclaration).initializer; case SyntaxKind.BinaryExpression: return (ancestor as BinaryExpression).right; } } function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { const parent = node.parent; if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) { return getLiteralPropertyNameText((node as BindingElement).propertyName || (node as BindingElement).name as Identifier); } if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) { return getLiteralPropertyNameText((node as PropertyAssignment | ShorthandPropertyAssignment).name); } return "" + ((parent as BindingPattern | ArrayLiteralExpression).elements as NodeArray).indexOf(node); } function getLiteralPropertyNameText(name: PropertyName) { const type = getLiteralTypeFromPropertyName(name); return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type as StringLiteralType | NumberLiteralType).value : undefined; } /** Return the inferred type for a binding element */ function getTypeForBindingElement(declaration: BindingElement): Type | undefined { const checkMode = declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal; const parentType = getTypeForBindingElementParent(declaration.parent.parent, checkMode); return parentType && getBindingElementTypeFromParentType(declaration, parentType, /*noTupleBoundsCheck*/ false); } function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type, noTupleBoundsCheck: boolean): Type { // If an any type was inferred for parent, infer that for the binding element if (isTypeAny(parentType)) { return parentType; } const pattern = declaration.parent; // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isPartOfParameterDeclaration(declaration)) { parentType = getNonNullableType(parentType); } // Filter `undefined` from the type we check against if the parent has an initializer and that initializer is not possibly `undefined` else if (strictNullChecks && pattern.parent.initializer && !(hasTypeFacts(getTypeOfInitializer(pattern.parent.initializer), TypeFacts.EQUndefined))) { parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); } const accessFlags = AccessFlags.ExpressionPosition | (noTupleBoundsCheck || hasDefaultValue(declaration) ? AccessFlags.AllowMissing : 0); let type: Type | undefined; if (pattern.kind === SyntaxKind.ObjectBindingPattern) { if (declaration.dotDotDotToken) { parentType = getReducedType(parentType); if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) { error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types); return errorType; } const literalMembers: PropertyName[] = []; for (const element of pattern.elements) { if (!element.dotDotDotToken) { literalMembers.push(element.propertyName || element.name as Identifier); } } type = getRestType(parentType, literalMembers, declaration.symbol); } else { // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) const name = declaration.propertyName || declaration.name as Identifier; const indexType = getLiteralTypeFromPropertyName(name); const declaredType = getIndexedAccessType(parentType, indexType, accessFlags, name); type = getFlowTypeOfDestructuring(declaration, declaredType); } } else { // This elementType will be used if the specific property corresponding to this index is not // present (aka the tuple element property). This call also checks that the parentType is in // fact an iterable or array (depending on target language). const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring | (declaration.dotDotDotToken ? 0 : IterationUse.PossiblyOutOfBounds), parentType, undefinedType, pattern); const index = pattern.elements.indexOf(declaration); if (declaration.dotDotDotToken) { // If the parent is a tuple type, the rest element has a tuple type of the // remaining tuple element types. Otherwise, the rest element has an array type with same // element type as the parent type. const baseConstraint = mapType(parentType, t => t.flags & TypeFlags.InstantiableNonPrimitive ? getBaseConstraintOrType(t) : t); type = everyType(baseConstraint, isTupleType) ? mapType(baseConstraint, t => sliceTupleType(t as TupleTypeReference, index)) : createArrayType(elementType); } else if (isArrayLikeType(parentType)) { const indexType = getNumberLiteralType(index); const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, accessFlags, declaration.name) || errorType; type = getFlowTypeOfDestructuring(declaration, declaredType); } else { type = elementType; } } if (!declaration.initializer) { return type; } if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) { // In strict null checking mode, if a default value of a non-undefined type is specified, remove // undefined from the final type. return strictNullChecks && !(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)) ? getNonUndefinedType(type) : type; } return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration, CheckMode.Normal)], UnionReduction.Subtype)); } function getTypeForDeclarationFromJSDocComment(declaration: Node) { const jsdocType = getJSDocType(declaration); if (jsdocType) { return getTypeFromTypeNode(jsdocType); } return undefined; } function isNullOrUndefined(node: Expression) { const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr as Identifier) === undefinedSymbol; } function isEmptyArrayLiteral(node: Expression) { const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr as ArrayLiteralExpression).elements.length === 0; } function addOptionality(type: Type, isProperty = false, isOptional = true): Type { return strictNullChecks && isOptional ? getOptionalType(type, isProperty) : type; } // Return the inferred type for a variable, parameter, or property declaration function getTypeForVariableLikeDeclaration( declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, includeOptionality: boolean, checkMode: CheckMode, ): Type | undefined { // A variable declared in a for..in statement is of type string, or of type keyof T when the // right hand expression is of a type parameter type. if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) { const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression, /*checkMode*/ checkMode))); return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType; } if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { // checkRightHandSideOfForOf will return undefined if the for-of expression type was // missing properties/signatures required to get its iteratedType (like // [Symbol.iterator] or next). This may be because we accessed properties from anyType, // or it may have led to an error inside getElementTypeOfIterable. const forOfStatement = declaration.parent.parent; return checkRightHandSideOfForOf(forOfStatement) || anyType; } if (isBindingPattern(declaration.parent)) { return getTypeForBindingElement(declaration as BindingElement); } const isProperty = (isPropertyDeclaration(declaration) && !hasAccessorModifier(declaration)) || isPropertySignature(declaration) || isJSDocPropertyTag(declaration); const isOptional = includeOptionality && isOptionalDeclaration(declaration); // Use type from type annotation if one is present const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { if (declaredType) { // If the catch clause is explicitly annotated with any or unknown, accept it, otherwise error. return isTypeAny(declaredType) || declaredType === unknownType ? declaredType : errorType; } // If the catch clause is not explicitly annotated, treat it as though it were explicitly // annotated with unknown or any, depending on useUnknownInCatchVariables. return useUnknownInCatchVariables ? unknownType : anyType; } if (declaredType) { return addOptionality(declaredType, isProperty, isOptional); } if ( (noImplicitAny || isInJSFile(declaration)) && isVariableDeclaration(declaration) && !isBindingPattern(declaration.name) && !(getCombinedModifierFlagsCached(declaration) & ModifierFlags.Export) && !(declaration.flags & NodeFlags.Ambient) ) { // If --noImplicitAny is on or the declaration is in a Javascript file, // use control flow tracked 'any' type for non-ambient, non-exported var or let variables with no // initializer or a 'null' or 'undefined' initializer. if (!(getCombinedNodeFlagsCached(declaration) & NodeFlags.Constant) && (!declaration.initializer || isNullOrUndefined(declaration.initializer))) { return autoType; } // Use control flow tracked 'any[]' type for non-ambient, non-exported variables with an empty array // literal initializer. if (declaration.initializer && isEmptyArrayLiteral(declaration.initializer)) { return autoArrayType; } } if (isParameter(declaration)) { if (!declaration.symbol) { // parameters of function types defined in JSDoc in TS files don't have symbols return; } const func = declaration.parent as FunctionLikeDeclaration; // For a parameter of a set accessor, use the type of the get accessor if one is present if (func.kind === SyntaxKind.SetAccessor && hasBindableName(func)) { const getter = getDeclarationOfKind(getSymbolOfDeclaration(declaration.parent), SyntaxKind.GetAccessor); if (getter) { const getterSignature = getSignatureFromDeclaration(getter); const thisParameter = getAccessorThisParameter(func as AccessorDeclaration); if (thisParameter && declaration === thisParameter) { // Use the type from the *getter* Debug.assert(!thisParameter.type); return getTypeOfSymbol(getterSignature.thisParameter!); } return getReturnTypeOfSignature(getterSignature); } } const parameterTypeOfTypeTag = getParameterTypeOfTypeTag(func, declaration); if (parameterTypeOfTypeTag) return parameterTypeOfTypeTag; // Use contextual parameter type if one is available const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration); if (type) { return addOptionality(type, /*isProperty*/ false, isOptional); } } // Use the type of the initializer expression if one is present and the declaration is // not a parameter of a contextually typed function if (hasOnlyExpressionInitializer(declaration) && !!declaration.initializer) { if (isInJSFile(declaration) && !isParameter(declaration)) { const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration)); if (containerObjectType) { return containerObjectType; } } const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); return addOptionality(type, isProperty, isOptional); } if (isPropertyDeclaration(declaration) && (noImplicitAny || isInJSFile(declaration))) { // We have a property declaration with no type annotation or initializer, in noImplicitAny mode or a .js file. // Use control flow analysis of this.xxx assignments in the constructor or static block to determine the type of the property. if (!hasStaticModifier(declaration)) { const constructor = findConstructorDeclaration(declaration.parent); const type = constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : undefined; return type && addOptionality(type, /*isProperty*/ true, isOptional); } else { const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); const type = staticBlocks.length ? getFlowTypeInStaticBlocks(declaration.symbol, staticBlocks) : getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : undefined; return type && addOptionality(type, /*isProperty*/ true, isOptional); } } if (isJsxAttribute(declaration)) { // if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true. // I.e is sugar for return trueType; } // If the declaration specifies a binding pattern and is not a parameter of a contextually // typed function, use the type implied by the binding pattern if (isBindingPattern(declaration.name)) { return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); } // No type specified and nothing can be inferred return undefined; } function isConstructorDeclaredProperty(symbol: Symbol) { // A property is considered a constructor declared property when all declaration sites are this.xxx assignments, // when no declaration sites have JSDoc type annotations, and when at least one declaration site is in the body of // a class constructor. if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration)) { const links = getSymbolLinks(symbol); if (links.isConstructorDeclaredProperty === undefined) { links.isConstructorDeclaredProperty = false; links.isConstructorDeclaredProperty = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration => isBinaryExpression(declaration) && isPossiblyAliasedThisProperty(declaration) && (declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((declaration.left as ElementAccessExpression).argumentExpression)) && !getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration)); } return links.isConstructorDeclaredProperty; } return false; } function isAutoTypedProperty(symbol: Symbol) { // A property is auto-typed when its declaration has no type annotation or initializer and we're in // noImplicitAny mode or a .js file. const declaration = symbol.valueDeclaration; return declaration && isPropertyDeclaration(declaration) && !getEffectiveTypeAnnotationNode(declaration) && !declaration.initializer && (noImplicitAny || isInJSFile(declaration)); } function getDeclaringConstructor(symbol: Symbol) { if (!symbol.declarations) { return; } for (const declaration of symbol.declarations) { const container = getThisContainer(declaration, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); if (container && (container.kind === SyntaxKind.Constructor || isJSConstructor(container))) { return container as ConstructorDeclaration; } } } /** Create a synthetic property access flow node after the last statement of the file */ function getFlowTypeFromCommonJSExport(symbol: Symbol) { const file = getSourceFileOfNode(symbol.declarations![0]); const accessName = unescapeLeadingUnderscores(symbol.escapedName); const areAllModuleExports = symbol.declarations!.every(d => isInJSFile(d) && isAccessExpression(d) && isModuleExportsAccessExpression(d.expression)); const reference = areAllModuleExports ? factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier("module"), factory.createIdentifier("exports")), accessName) : factory.createPropertyAccessExpression(factory.createIdentifier("exports"), accessName); if (areAllModuleExports) { setParent((reference.expression as PropertyAccessExpression).expression, reference.expression); } setParent(reference.expression, reference); setParent(reference, file); reference.flowNode = file.endFlowNode; return getFlowTypeOfReference(reference, autoType, undefinedType); } function getFlowTypeInStaticBlocks(symbol: Symbol, staticBlocks: readonly ClassStaticBlockDeclaration[]) { const accessName = startsWith(symbol.escapedName as string, "__#") ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) : unescapeLeadingUnderscores(symbol.escapedName); for (const staticBlock of staticBlocks) { const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); setParent(reference.expression, reference); setParent(reference, staticBlock); reference.flowNode = staticBlock.returnFlowNode; const flowType = getFlowTypeOfProperty(reference, symbol); if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } // We don't infer a type if assignments are only null or undefined. if (everyType(flowType, isNullableType)) { continue; } return convertAutoToAny(flowType); } } function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) { const accessName = startsWith(symbol.escapedName as string, "__#") ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) : unescapeLeadingUnderscores(symbol.escapedName); const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); setParent(reference.expression, reference); setParent(reference, constructor); reference.flowNode = constructor.returnFlowNode; const flowType = getFlowTypeOfProperty(reference, symbol); if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } // We don't infer a type if assignments are only null or undefined. return everyType(flowType, isNullableType) ? undefined : convertAutoToAny(flowType); } function getFlowTypeOfProperty(reference: Node, prop: Symbol | undefined) { const initialType = prop?.valueDeclaration && (!isAutoTypedProperty(prop) || getEffectiveModifierFlags(prop.valueDeclaration) & ModifierFlags.Ambient) && getTypeOfPropertyInBaseClass(prop) || undefinedType; return getFlowTypeOfReference(reference, autoType, initialType); } function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { // function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers const container = getAssignedExpandoInitializer(symbol.valueDeclaration); if (container) { const tag = isInJSFile(container) ? getJSDocTypeTag(container) : undefined; if (tag && tag.typeExpression) { return getTypeFromTypeNode(tag.typeExpression); } const containerObjectType = symbol.valueDeclaration && getJSContainerObjectType(symbol.valueDeclaration, symbol, container); return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); } let type; let definedInConstructor = false; let definedInMethod = false; // We use control flow analysis to determine the type of the property if the property qualifies as a constructor // declared property and the resulting control flow type isn't just undefined or null. if (isConstructorDeclaredProperty(symbol)) { type = getFlowTypeInConstructor(symbol, getDeclaringConstructor(symbol)!); } if (!type) { let types: Type[] | undefined; if (symbol.declarations) { let jsdocType: Type | undefined; for (const declaration of symbol.declarations) { const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : undefined; if (!expression) { continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere } const kind = isAccessExpression(expression) ? getAssignmentDeclarationPropertyAccessKind(expression) : getAssignmentDeclarationKind(expression); if (kind === AssignmentDeclarationKind.ThisProperty || isBinaryExpression(expression) && isPossiblyAliasedThisProperty(expression, kind)) { if (isDeclarationInConstructor(expression)) { definedInConstructor = true; } else { definedInMethod = true; } } if (!isCallExpression(expression)) { jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); } if (!jsdocType) { (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); } } type = jsdocType; } if (!type) { if (!length(types)) { return errorType; // No types from any declarations :( } let constructorTypes = definedInConstructor && symbol.declarations ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; // use only the constructor types unless they were only assigned null | undefined (including widening variants) if (definedInMethod) { const propType = getTypeOfPropertyInBaseClass(symbol); if (propType) { (constructorTypes || (constructorTypes = [])).push(propType); definedInConstructor = true; } } const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 type = getUnionType(sourceTypes!); } } const widened = getWidenedType(addOptionality(type, /*isProperty*/ false, definedInMethod && !definedInConstructor)); if (symbol.valueDeclaration && isInJSFile(symbol.valueDeclaration) && filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) { reportImplicitAny(symbol.valueDeclaration, anyType); return anyType; } return widened; } function getJSContainerObjectType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined { if (!isInJSFile(decl) || !init || !isObjectLiteralExpression(init) || init.properties.length) { return undefined; } const exports = createSymbolTable(); while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) { const s = getSymbolOfNode(decl); if (s?.exports?.size) { mergeSymbolTable(exports, s.exports); } decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent; } const s = getSymbolOfNode(decl); if (s?.exports?.size) { mergeSymbolTable(exports, s.exports); } const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, emptyArray); type.objectFlags |= ObjectFlags.JSLiteral; return type; } function getAnnotatedTypeForAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, symbol: Symbol, declaration: Declaration) { const typeNode = getEffectiveTypeAnnotationNode(expression.parent); if (typeNode) { const type = getWidenedType(getTypeFromTypeNode(typeNode)); if (!declaredType) { return type; } else if (!isErrorType(declaredType) && !isErrorType(type) && !isTypeIdenticalTo(declaredType, type)) { errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); } } if (symbol.parent?.valueDeclaration) { const possiblyAnnotatedSymbol = getFunctionExpressionParentSymbolOrSymbol(symbol.parent); if (possiblyAnnotatedSymbol.valueDeclaration) { const typeNode = getEffectiveTypeAnnotationNode(possiblyAnnotatedSymbol.valueDeclaration); if (typeNode) { const annotationSymbol = getPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); if (annotationSymbol) { return getNonMissingTypeOfSymbol(annotationSymbol); } } } } return declaredType; } /** If we don't have an explicit JSDoc type, get the type from the initializer. */ function getInitializerTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression | CallExpression, kind: AssignmentDeclarationKind) { if (isCallExpression(expression)) { if (resolvedSymbol) { return getTypeOfSymbol(resolvedSymbol); // This shouldn't happen except under some hopefully forbidden merges of export assignments and object define assignments } const objectLitType = checkExpressionCached((expression as BindableObjectDefinePropertyCall).arguments[2]); const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); if (valueType) { return valueType; } const getFunc = getTypeOfPropertyOfType(objectLitType, "get" as __String); if (getFunc) { const getSig = getSingleCallSignature(getFunc); if (getSig) { return getReturnTypeOfSignature(getSig); } } const setFunc = getTypeOfPropertyOfType(objectLitType, "set" as __String); if (setFunc) { const setSig = getSingleCallSignature(setFunc); if (setSig) { return getTypeOfFirstParameterOfSignature(setSig); } } return anyType; } if (containsSameNamedThisProperty(expression.left, expression.right)) { return anyType; } const isDirectExport = kind === AssignmentDeclarationKind.ExportsProperty && (isPropertyAccessExpression(expression.left) || isElementAccessExpression(expression.left)) && (isModuleExportsAccessExpression(expression.left.expression) || (isIdentifier(expression.left.expression) && isExportsIdentifier(expression.left.expression))); const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : isDirectExport ? getRegularTypeOfLiteralType(checkExpressionCached(expression.right)) : getWidenedLiteralType(checkExpressionCached(expression.right)); if ( type.flags & TypeFlags.Object && kind === AssignmentDeclarationKind.ModuleExports && symbol.escapedName === InternalSymbolName.ExportEquals ) { const exportedType = resolveStructuredTypeMembers(type as ObjectType); const members = createSymbolTable(); copyEntries(exportedType.members, members); const initialSize = members.size; if (resolvedSymbol && !resolvedSymbol.exports) { resolvedSymbol.exports = createSymbolTable(); } (resolvedSymbol || symbol).exports!.forEach((s, name) => { const exportedMember = members.get(name)!; if (exportedMember && exportedMember !== s && !(s.flags & SymbolFlags.Alias)) { if (s.flags & SymbolFlags.Value && exportedMember.flags & SymbolFlags.Value) { // If the member has an additional value-like declaration, union the types from the two declarations, // but issue an error if they occurred in two different files. The purpose is to support a JS file with // a pattern like: // // module.exports = { a: true }; // module.exports.a = 3; // // but we may have a JS file with `module.exports = { a: true }` along with a TypeScript module augmentation // declaring an `export const a: number`. In that case, we issue a duplicate identifier error, because // it's unclear what that's supposed to mean, so it's probably a mistake. if (s.valueDeclaration && exportedMember.valueDeclaration && getSourceFileOfNode(s.valueDeclaration) !== getSourceFileOfNode(exportedMember.valueDeclaration)) { const unescapedName = unescapeLeadingUnderscores(s.escapedName); const exportedMemberName = tryCast(exportedMember.valueDeclaration, isNamedDeclaration)?.name || exportedMember.valueDeclaration; addRelatedInfo( error(s.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapedName), createDiagnosticForNode(exportedMemberName, Diagnostics._0_was_also_declared_here, unescapedName), ); addRelatedInfo( error(exportedMemberName, Diagnostics.Duplicate_identifier_0, unescapedName), createDiagnosticForNode(s.valueDeclaration, Diagnostics._0_was_also_declared_here, unescapedName), ); } const union = createSymbol(s.flags | exportedMember.flags, name); union.links.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]); union.valueDeclaration = exportedMember.valueDeclaration; union.declarations = concatenate(exportedMember.declarations, s.declarations); members.set(name, union); } else { members.set(name, mergeSymbol(s, exportedMember)); } } else { members.set(name, s); } }); const result = createAnonymousType( initialSize !== members.size ? undefined : exportedType.symbol, // Only set the type's symbol if it looks to be the same as the original type members, exportedType.callSignatures, exportedType.constructSignatures, exportedType.indexInfos, ); if (initialSize === members.size) { if (type.aliasSymbol) { result.aliasSymbol = type.aliasSymbol; result.aliasTypeArguments = type.aliasTypeArguments; } if (getObjectFlags(type) & ObjectFlags.Reference) { result.aliasSymbol = (type as TypeReference).symbol; const args = getTypeArguments(type as TypeReference); result.aliasTypeArguments = length(args) ? args : undefined; } } result.objectFlags |= getPropagatingFlagsOfTypes([type]) | getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.ArrayLiteral | ObjectFlags.ObjectLiteral); if (result.symbol && result.symbol.flags & SymbolFlags.Class && type === getDeclaredTypeOfClassOrInterface(result.symbol)) { result.objectFlags |= ObjectFlags.IsClassInstanceClone; // Propagate the knowledge that this type is equivalent to the symbol's class instance type } return result; } if (isEmptyArrayLiteralType(type)) { reportImplicitAny(expression, anyArrayType); return anyArrayType; } return type; } function containsSameNamedThisProperty(thisProperty: Expression, expression: Expression) { return isPropertyAccessExpression(thisProperty) && thisProperty.expression.kind === SyntaxKind.ThisKeyword && forEachChildRecursively(expression, n => isMatchingReference(thisProperty, n)); } function isDeclarationInConstructor(expression: Expression) { const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); // Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added. // Function expressions that are assigned to the prototype count as methods. return thisContainer.kind === SyntaxKind.Constructor || thisContainer.kind === SyntaxKind.FunctionDeclaration || (thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent)); } function getConstructorDefinedThisAssignmentTypes(types: Type[], declarations: Declaration[]): Type[] | undefined { Debug.assert(types.length === declarations.length); return types.filter((_, i) => { const declaration = declarations[i]; const expression = isBinaryExpression(declaration) ? declaration : isBinaryExpression(declaration.parent) ? declaration.parent : undefined; return expression && isDeclarationInConstructor(expression); }); } // Return the type implied by a binding pattern element. This is the type of the initializer of the element if // one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding // pattern. Otherwise, it is the type any. function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type { if (element.initializer) { // The type implied by a binding pattern is independent of context, so we check the initializer with no // contextual type or, if the element itself is a binding pattern, with the type implied by that binding // pattern. const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType; return addOptionality(getWidenedLiteralTypeForInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType))); } if (isBindingPattern(element.name)) { return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); } if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) { reportImplicitAny(element, anyType); } // When we're including the pattern in the type (an indication we're obtaining a contextual type), we // use a non-inferrable any type. Inference will never directly infer this type, but it is possible // to infer a type that contains it, e.g. for a binding pattern like [foo] or { foo }. In such cases, // widening of the binding pattern type substitutes a regular any for the non-inferrable any. return includePatternInType ? nonInferrableAnyType : anyType; } // Return the type implied by an object binding pattern function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { const members = createSymbolTable(); let stringIndexInfo: IndexInfo | undefined; let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; forEach(pattern.elements, e => { const name = e.propertyName || e.name as Identifier; if (e.dotDotDotToken) { stringIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); return; } const exprType = getLiteralTypeFromPropertyName(name); if (!isTypeUsableAsPropertyName(exprType)) { // do not include computed properties in the implied type objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; return; } const text = getPropertyNameFromType(exprType); const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); const symbol = createSymbol(flags, text); symbol.links.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); members.set(symbol.escapedName, symbol); }); const result = createAnonymousType(/*symbol*/ undefined, members, emptyArray, emptyArray, stringIndexInfo ? [stringIndexInfo] : emptyArray); result.objectFlags |= objectFlags; if (includePatternInType) { result.pattern = pattern; result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; } return result; } // Return the type implied by an array binding pattern function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { const elements = pattern.elements; const lastElement = lastOrUndefined(elements); const restElement = lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken ? lastElement : undefined; if (elements.length === 0 || elements.length === 1 && restElement) { return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType; } const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); const minLength = findLastIndex(elements, e => !(e === restElement || isOmittedExpression(e) || hasDefaultValue(e)), elements.length - 1) + 1; const elementFlags = map(elements, (e, i) => e === restElement ? ElementFlags.Rest : i >= minLength ? ElementFlags.Optional : ElementFlags.Required); let result = createTupleType(elementTypes, elementFlags) as TypeReference; if (includePatternInType) { result = cloneTypeReference(result); result.pattern = pattern; result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; } return result; } // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself // and without regard to its context (i.e. without regard any type annotation or initializer associated with the // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of // the parameter. function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type { if (includePatternInType) contextualBindingPatterns.push(pattern); const result = pattern.kind === SyntaxKind.ObjectBindingPattern ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors) : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors); if (includePatternInType) contextualBindingPatterns.pop(); return result; } // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type // specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it // is a bit more involved. For example: // // var [x, s = ""] = [1, "one"]; // // Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, reportErrors?: boolean): Type { return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors); } function getTypeFromImportAttributes(node: ImportAttributes): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const symbol = createSymbol(SymbolFlags.ObjectLiteral, InternalSymbolName.ImportAttributes); const members = createSymbolTable(); forEach(node.elements, attr => { const member = createSymbol(SymbolFlags.Property, getNameFromImportAttribute(attr)); member.parent = symbol; member.links.type = checkImportAttribute(attr); member.links.target = member; members.set(member.escapedName, member); }); const type = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); type.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.NonInferrableType; links.resolvedType = type; } return links.resolvedType; } function isGlobalSymbolConstructor(node: Node) { const symbol = getSymbolOfNode(node); const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false); return globalSymbol && symbol && symbol === globalSymbol; } function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) { if (type) { // TODO: If back compat with pre-3.0/4.0 libs isn't required, remove the following SymbolConstructor special case transforming `symbol` into `unique symbol` if (type.flags & TypeFlags.ESSymbol && isGlobalSymbolConstructor(declaration.parent)) { type = getESSymbolLikeTypeForNode(declaration); } if (reportErrors) { reportErrorsFromWidening(declaration, type); } // always widen a 'unique symbol' type if the type was created for a different declaration. if (type.flags & TypeFlags.UniqueESSymbol && (isBindingElement(declaration) || !tryGetTypeFromEffectiveTypeNode(declaration)) && type.symbol !== getSymbolOfDeclaration(declaration)) { type = esSymbolType; } return getWidenedType(type); } // Rest parameters default to type any[], other parameters default to type any type = isParameter(declaration) && declaration.dotDotDotToken ? anyArrayType : anyType; // Report implicit any errors unless this is a private property within an ambient declaration if (reportErrors) { if (!declarationBelongsToPrivateAmbientMember(declaration)) { reportImplicitAny(declaration, type); } } return type; } function declarationBelongsToPrivateAmbientMember(declaration: VariableLikeDeclaration) { const root = getRootDeclaration(declaration); const memberDeclaration = root.kind === SyntaxKind.Parameter ? root.parent : root; return isPrivateWithinAmbient(memberDeclaration); } function tryGetTypeFromEffectiveTypeNode(node: Node) { const typeNode = getEffectiveTypeAnnotationNode(node); if (typeNode) { return getTypeFromTypeNode(typeNode); } } function isParameterOfContextSensitiveSignature(symbol: Symbol) { let decl = symbol.valueDeclaration; if (!decl) { return false; } if (isBindingElement(decl)) { decl = walkUpBindingElementsAndPatterns(decl); } if (isParameter(decl)) { return isContextSensitiveFunctionOrObjectLiteralMethod(decl.parent); } return false; } function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.type) { const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol); // For a contextually typed parameter it is possible that a type has already // been assigned (in assignTypeToParameterAndFixTypeParameters), and we want // to preserve this type. In fact, we need to _prefer_ that type, but it won't // be assigned until contextual typing is complete, so we need to defer in // cases where contextual typing may take place. if (!links.type && !isParameterOfContextSensitiveSignature(symbol)) { links.type = type; } return type; } return links.type; } function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol): Type { // Handle prototype property if (symbol.flags & SymbolFlags.Prototype) { return getTypeOfPrototypeProperty(symbol); } // CommonsJS require and module both have type any. if (symbol === requireSymbol) { return anyType; } if (symbol.flags & SymbolFlags.ModuleExports && symbol.valueDeclaration) { const fileSymbol = getSymbolOfDeclaration(getSourceFileOfNode(symbol.valueDeclaration)); const result = createSymbol(fileSymbol.flags, "exports" as __String); result.declarations = fileSymbol.declarations ? fileSymbol.declarations.slice() : []; result.parent = symbol; result.links.target = fileSymbol; if (fileSymbol.valueDeclaration) result.valueDeclaration = fileSymbol.valueDeclaration; if (fileSymbol.members) result.members = new Map(fileSymbol.members); if (fileSymbol.exports) result.exports = new Map(fileSymbol.exports); const members = createSymbolTable(); members.set("exports" as __String, result); return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); } Debug.assertIsDefined(symbol.valueDeclaration); const declaration = symbol.valueDeclaration; // Handle export default expressions if (isSourceFile(declaration) && isJsonSourceFile(declaration)) { if (!declaration.statements.length) { return emptyObjectType; } return getWidenedType(getWidenedLiteralType(checkExpression(declaration.statements[0].expression))); } if (isAccessor(declaration)) { // Binding of certain patterns in JS code will occasionally mark symbols as both properties // and accessors. Here we dispatch to accessor resolution if needed. return getTypeOfAccessors(symbol); } // Handle variable, parameter or property if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { return getTypeOfFuncClassEnumModule(symbol); } return reportCircularityError(symbol); } let type: Type; if (declaration.kind === SyntaxKind.ExportAssignment) { type = widenTypeForVariableLikeDeclaration(tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionCached((declaration as ExportAssignment).expression), declaration); } else if ( isBinaryExpression(declaration) || (isInJSFile(declaration) && (isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent))) ) { type = getWidenedTypeForAssignmentDeclaration(symbol); } else if ( isPropertyAccessExpression(declaration) || isElementAccessExpression(declaration) || isIdentifier(declaration) || isStringLiteralLike(declaration) || isNumericLiteral(declaration) || isClassDeclaration(declaration) || isFunctionDeclaration(declaration) || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) || isMethodSignature(declaration) || isSourceFile(declaration) ) { // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { return getTypeOfFuncClassEnumModule(symbol); } type = isBinaryExpression(declaration.parent) ? getWidenedTypeForAssignmentDeclaration(symbol) : tryGetTypeFromEffectiveTypeNode(declaration) || anyType; } else if (isPropertyAssignment(declaration)) { type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); } else if (isJsxAttribute(declaration)) { type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); } else if (isShorthandPropertyAssignment(declaration)) { type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); } else if (isObjectLiteralMethod(declaration)) { type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); } else if ( isParameter(declaration) || isPropertyDeclaration(declaration) || isPropertySignature(declaration) || isVariableDeclaration(declaration) || isBindingElement(declaration) || isJSDocPropertyLikeTag(declaration) ) { type = getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true); } // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. // Re-dispatch based on valueDeclaration.kind instead. else if (isEnumDeclaration(declaration)) { type = getTypeOfFuncClassEnumModule(symbol); } else if (isEnumMember(declaration)) { type = getTypeOfEnumMember(symbol); } else { return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol)); } if (!popTypeResolution()) { // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { return getTypeOfFuncClassEnumModule(symbol); } return reportCircularityError(symbol); } return type; } function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | PropertyDeclaration | undefined): TypeNode | undefined { if (accessor) { switch (accessor.kind) { case SyntaxKind.GetAccessor: const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor); return getterTypeAnnotation; case SyntaxKind.SetAccessor: const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor); return setterTypeAnnotation; case SyntaxKind.PropertyDeclaration: Debug.assert(hasAccessorModifier(accessor)); const accessorTypeAnnotation = getEffectiveTypeAnnotationNode(accessor); return accessorTypeAnnotation; } } return undefined; } function getAnnotatedAccessorType(accessor: AccessorDeclaration | PropertyDeclaration | undefined): Type | undefined { const node = getAnnotatedAccessorTypeNode(accessor); return node && getTypeFromTypeNode(node); } function getAnnotatedAccessorThisParameter(accessor: AccessorDeclaration): Symbol | undefined { const parameter = getAccessorThisParameter(accessor); return parameter && parameter.symbol; } function getThisTypeOfDeclaration(declaration: SignatureDeclaration): Type | undefined { return getThisTypeOfSignature(getSignatureFromDeclaration(declaration)); } function getTypeOfAccessors(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.type) { if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { return errorType; } const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); const accessor = tryCast(getDeclarationOfKind(symbol, SyntaxKind.PropertyDeclaration), isAutoAccessorPropertyDeclaration); // We try to resolve a getter type annotation, a setter type annotation, or a getter function // body return type inference, in that order. let type = getter && isInJSFile(getter) && getTypeForDeclarationFromJSDocComment(getter) || getAnnotatedAccessorType(getter) || getAnnotatedAccessorType(setter) || getAnnotatedAccessorType(accessor) || getter && getter.body && getReturnTypeFromBody(getter) || accessor && getWidenedTypeForVariableLikeDeclaration(accessor, /*reportErrors*/ true); if (!type) { if (setter && !isPrivateWithinAmbient(setter)) { errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); } else if (getter && !isPrivateWithinAmbient(getter)) { errorOrSuggestion(noImplicitAny, getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); } else if (accessor && !isPrivateWithinAmbient(accessor)) { errorOrSuggestion(noImplicitAny, accessor, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), "any"); } type = anyType; } if (!popTypeResolution()) { if (getAnnotatedAccessorTypeNode(getter)) { error(getter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); } else if (getAnnotatedAccessorTypeNode(setter)) { error(setter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); } else if (getAnnotatedAccessorTypeNode(accessor)) { error(setter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); } else if (getter && noImplicitAny) { error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); } type = anyType; } links.type ??= type; } return links.type; } function getWriteTypeOfAccessors(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.writeType) { if (!pushTypeResolution(symbol, TypeSystemPropertyName.WriteType)) { return errorType; } const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor) ?? tryCast(getDeclarationOfKind(symbol, SyntaxKind.PropertyDeclaration), isAutoAccessorPropertyDeclaration); let writeType = getAnnotatedAccessorType(setter); if (!popTypeResolution()) { if (getAnnotatedAccessorTypeNode(setter)) { error(setter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); } writeType = anyType; } // Absent an explicit setter type annotation we use the read type of the accessor. links.writeType ??= writeType || getTypeOfAccessors(symbol); } return links.writeType; } function getBaseTypeVariableOfClass(symbol: Symbol) { const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol)); return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) : undefined; } function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { let links = getSymbolLinks(symbol); const originalLinks = links; if (!links.type) { const expando = symbol.valueDeclaration && getSymbolOfExpando(symbol.valueDeclaration, /*allowDeclaration*/ false); if (expando) { const merged = mergeJSSymbols(symbol, expando); if (merged) { // note:we overwrite links because we just cloned the symbol symbol = merged; links = merged.links; } } originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); } return links.type; } function getTypeOfFuncClassEnumModuleWorker(symbol: Symbol): Type { const declaration = symbol.valueDeclaration; if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) { return anyType; } else if ( declaration && (declaration.kind === SyntaxKind.BinaryExpression || isAccessExpression(declaration) && declaration.parent.kind === SyntaxKind.BinaryExpression) ) { return getWidenedTypeForAssignmentDeclaration(symbol); } else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) { const resolvedModule = resolveExternalModuleSymbol(symbol); if (resolvedModule !== symbol) { if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { return errorType; } const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!); const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); if (!popTypeResolution()) { return reportCircularityError(symbol); } return type; } } const type = createObjectType(ObjectFlags.Anonymous, symbol); if (symbol.flags & SymbolFlags.Class) { const baseTypeVariable = getBaseTypeVariableOfClass(symbol); return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type; } else { return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type, /*isProperty*/ true) : type; } } function getTypeOfEnumMember(symbol: Symbol): Type { const links = getSymbolLinks(symbol); return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol)); } function getTypeOfAlias(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.type) { if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { return errorType; } const targetSymbol = resolveAlias(symbol); const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontRecursivelyResolve*/ true); const declaredType = firstDefined(exportSymbol?.declarations, d => isExportAssignment(d) ? tryGetTypeFromEffectiveTypeNode(d) : undefined); // It only makes sense to get the type of a value symbol. If the result of resolving // the alias is not a value, then it has no type. To get the type associated with a // type symbol, call getDeclaredTypeOfSymbol. // This check is important because without it, a call to getTypeOfSymbol could end // up recursively calling getTypeOfAlias, causing a stack overflow. links.type ??= exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol) : isDuplicatedCommonJSExport(symbol.declarations) ? autoType : declaredType ? declaredType : getSymbolFlags(targetSymbol) & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol) : errorType; if (!popTypeResolution()) { reportCircularityError(exportSymbol ?? symbol); return links.type ??= errorType; } } return links.type; } function getTypeOfInstantiatedSymbol(symbol: Symbol): Type { const links = getSymbolLinks(symbol); return links.type || (links.type = instantiateType(getTypeOfSymbol(links.target!), links.mapper)); } function getWriteTypeOfInstantiatedSymbol(symbol: Symbol): Type { const links = getSymbolLinks(symbol); return links.writeType || (links.writeType = instantiateType(getWriteTypeOfSymbol(links.target!), links.mapper)); } function reportCircularityError(symbol: Symbol) { const declaration = symbol.valueDeclaration; // Check if variable has type annotation that circularly references the variable itself if (declaration) { if (getEffectiveTypeAnnotationNode(declaration)) { error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); return errorType; } // Check if variable has initializer that circularly references the variable itself if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (declaration as HasInitializer).initializer)) { error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, symbolToString(symbol)); } } else if (symbol.flags & SymbolFlags.Alias) { const node = getDeclarationOfAliasSymbol(symbol); if (node) { error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); } } // Circularities could also result from parameters in function expressions that end up // having themselves as contextual types following type argument inference. In those cases // we have already reported an implicit any error so we don't report anything here. return anyType; } function getTypeOfSymbolWithDeferredType(symbol: Symbol) { const links = getSymbolLinks(symbol); if (!links.type) { Debug.assertIsDefined(links.deferralParent); Debug.assertIsDefined(links.deferralConstituents); links.type = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents) : getIntersectionType(links.deferralConstituents); } return links.type; } function getWriteTypeOfSymbolWithDeferredType(symbol: Symbol): Type | undefined { const links = getSymbolLinks(symbol); if (!links.writeType && links.deferralWriteConstituents) { Debug.assertIsDefined(links.deferralParent); Debug.assertIsDefined(links.deferralConstituents); links.writeType = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralWriteConstituents) : getIntersectionType(links.deferralWriteConstituents); } return links.writeType; } /** * Distinct write types come only from set accessors, but synthetic union and intersection * properties deriving from set accessors will either pre-compute or defer the union or * intersection of the writeTypes of their constituents. */ function getWriteTypeOfSymbol(symbol: Symbol): Type { const checkFlags = getCheckFlags(symbol); if (checkFlags & CheckFlags.SyntheticProperty) { return checkFlags & CheckFlags.DeferredType ? getWriteTypeOfSymbolWithDeferredType(symbol) || getTypeOfSymbolWithDeferredType(symbol) : // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty (symbol as TransientSymbol).links.writeType || (symbol as TransientSymbol).links.type!; } if (symbol.flags & SymbolFlags.Property) { return removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional)); } if (symbol.flags & SymbolFlags.Accessor) { return checkFlags & CheckFlags.Instantiated ? getWriteTypeOfInstantiatedSymbol(symbol) : getWriteTypeOfAccessors(symbol); } return getTypeOfSymbol(symbol); } function getTypeOfSymbol(symbol: Symbol): Type { const checkFlags = getCheckFlags(symbol); if (checkFlags & CheckFlags.DeferredType) { return getTypeOfSymbolWithDeferredType(symbol); } if (checkFlags & CheckFlags.Instantiated) { return getTypeOfInstantiatedSymbol(symbol); } if (checkFlags & CheckFlags.Mapped) { return getTypeOfMappedSymbol(symbol as MappedSymbol); } if (checkFlags & CheckFlags.ReverseMapped) { return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol); } if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { return getTypeOfVariableOrParameterOrProperty(symbol); } if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { return getTypeOfFuncClassEnumModule(symbol); } if (symbol.flags & SymbolFlags.EnumMember) { return getTypeOfEnumMember(symbol); } if (symbol.flags & SymbolFlags.Accessor) { return getTypeOfAccessors(symbol); } if (symbol.flags & SymbolFlags.Alias) { return getTypeOfAlias(symbol); } return errorType; } function getNonMissingTypeOfSymbol(symbol: Symbol) { return removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional)); } function isReferenceToSomeType(type: Type, targets: readonly Type[]) { if (type === undefined || (getObjectFlags(type) & ObjectFlags.Reference) === 0) { return false; } for (const target of targets) { if ((type as TypeReference).target === target) { return true; } } return false; } function isReferenceToType(type: Type, target: Type) { return type !== undefined && target !== undefined && (getObjectFlags(type) & ObjectFlags.Reference) !== 0 && (type as TypeReference).target === target; } function getTargetType(type: Type): Type { return getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target : type; } // TODO: GH#18217 If `checkBase` is undefined, we should not call this because this will always return false. function hasBaseType(type: Type, checkBase: Type | undefined) { return check(type); function check(type: Type): boolean { if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { const target = getTargetType(type) as InterfaceType; return target === checkBase || some(getBaseTypes(target), check); } else if (type.flags & TypeFlags.Intersection) { return some((type as IntersectionType).types, check); } return false; } } // Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set. // The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set // in-place and returns the same array. function appendTypeParameters(typeParameters: TypeParameter[] | undefined, declarations: readonly TypeParameterDeclaration[]): TypeParameter[] | undefined { for (const declaration of declarations) { typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(declaration))); } return typeParameters; } // Return the outer type parameters of a node or undefined if the node has no outer type parameters. function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] | undefined { while (true) { node = node.parent; // TODO: GH#18217 Use SourceFile kind check instead if (node && isBinaryExpression(node)) { // prototype assignments get the outer type parameters of their constructor function const assignmentKind = getAssignmentDeclarationKind(node); if (assignmentKind === AssignmentDeclarationKind.Prototype || assignmentKind === AssignmentDeclarationKind.PrototypeProperty) { const symbol = getSymbolOfDeclaration(node.left as BindableStaticNameExpression | PropertyAssignment); if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { node = symbol.parent.valueDeclaration!; } } } if (!node) { return undefined; } const kind = node.kind; switch (kind) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.MethodSignature: case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.JSDocFunctionType: case SyntaxKind.FunctionDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.JSDocTemplateTag: case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocEnumTag: case SyntaxKind.JSDocCallbackTag: case SyntaxKind.MappedType: case SyntaxKind.ConditionalType: { const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); if ((kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction || isObjectLiteralMethod(node)) && isContextSensitive(node as Expression | MethodDeclaration)) { const signature = firstOrUndefined(getSignaturesOfType(getTypeOfSymbol(getSymbolOfDeclaration(node as FunctionLikeDeclaration)), SignatureKind.Call)); if (signature && signature.typeParameters) { return [...(outerTypeParameters || emptyArray), ...signature.typeParameters]; } } if (kind === SyntaxKind.MappedType) { return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration((node as MappedTypeNode).typeParameter))); } else if (kind === SyntaxKind.ConditionalType) { return concatenate(outerTypeParameters, getInferTypeParameters(node as ConditionalTypeNode)); } const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node as DeclarationWithTypeParameters)); const thisType = includeThisTypes && (kind === SyntaxKind.ClassDeclaration || kind === SyntaxKind.ClassExpression || kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType; return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; } case SyntaxKind.JSDocParameterTag: const paramSymbol = getParameterSymbolFromJSDoc(node as JSDocParameterTag); if (paramSymbol) { node = paramSymbol.valueDeclaration!; } break; case SyntaxKind.JSDoc: { const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); return (node as JSDoc).tags ? appendTypeParameters(outerTypeParameters, flatMap((node as JSDoc).tags, t => isJSDocTemplateTag(t) ? t.typeParameters : undefined)) : outerTypeParameters; } } } } // The outer type parameters are those defined by enclosing generic classes, methods, or functions. function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { const declaration = (symbol.flags & SymbolFlags.Class || symbol.flags & SymbolFlags.Function) ? symbol.valueDeclaration : symbol.declarations?.find(decl => { if (decl.kind === SyntaxKind.InterfaceDeclaration) { return true; } if (decl.kind !== SyntaxKind.VariableDeclaration) { return false; } const initializer = (decl as VariableDeclaration).initializer; return !!initializer && (initializer.kind === SyntaxKind.FunctionExpression || initializer.kind === SyntaxKind.ArrowFunction); })!; Debug.assert(!!declaration, "Class was missing valueDeclaration -OR- non-class had no interface declarations"); return getOuterTypeParameters(declaration); } // The local type parameters are the combined set of type parameters from all declarations of the class, // interface, or type alias. function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] | undefined { if (!symbol.declarations) { return; } let result: TypeParameter[] | undefined; for (const node of symbol.declarations) { if ( node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || isJSConstructor(node) || isTypeAlias(node) ) { const declaration = node as InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag; result = appendTypeParameters(result, getEffectiveTypeParameterDeclarations(declaration)); } } return result; } // The full set of type parameters for a generic class or interface type consists of its outer type parameters plus // its locally declared type parameters. function getTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)); } // A type is a mixin constructor if it has a single construct signature taking no type parameters and a single // rest parameter of type any[]. function isMixinConstructorType(type: Type) { const signatures = getSignaturesOfType(type, SignatureKind.Construct); if (signatures.length === 1) { const s = signatures[0]; if (!s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s)) { const paramType = getTypeOfParameter(s.parameters[0]); return isTypeAny(paramType) || getElementTypeOfArrayType(paramType) === anyType; } } return false; } function isConstructorType(type: Type): boolean { if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) { return true; } if (type.flags & TypeFlags.TypeVariable) { const constraint = getBaseConstraintOfType(type); return !!constraint && isMixinConstructorType(constraint); } return false; } function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { const decl = getClassLikeDeclarationOfSymbol(type.symbol); return decl && getEffectiveBaseTypeNode(decl); } function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { const typeArgCount = length(typeArgumentNodes); const isJavascript = isInJSFile(location); return filter(getSignaturesOfType(type, SignatureKind.Construct), sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount (signatures, sig => some(sig.typeParameters) ? getSignatureInstantiation(sig, typeArguments, isInJSFile(location)) : sig); } /** * The base constructor of a class can resolve to * * undefinedType if the class has no extends clause, * * errorType if an error occurred during resolution of the extends expression, * * nullType if the extends expression is the null value, * * anyType if the extends expression has type any, or * * an object type with at least one construct signature. */ function getBaseConstructorTypeOfClass(type: InterfaceType): Type { if (!type.resolvedBaseConstructorType) { const decl = getClassLikeDeclarationOfSymbol(type.symbol); const extended = decl && getEffectiveBaseTypeNode(decl); const baseTypeNode = getBaseTypeNodeOfClass(type); if (!baseTypeNode) { return type.resolvedBaseConstructorType = undefinedType; } if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseConstructorType)) { return errorType; } const baseConstructorType = checkExpression(baseTypeNode.expression); if (extended && baseTypeNode !== extended) { Debug.assert(!extended.typeArguments); // Because this is in a JS file, and baseTypeNode is in an @extends tag checkExpression(extended.expression); } if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection)) { // Resolving the members of a class requires us to resolve the base class of that class. // We force resolution here such that we catch circularities now. resolveStructuredTypeMembers(baseConstructorType as ObjectType); } if (!popTypeResolution()) { error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol)); return type.resolvedBaseConstructorType ??= errorType; } if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) { const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType)); if (baseConstructorType.flags & TypeFlags.TypeParameter) { const constraint = getConstraintFromTypeParameter(baseConstructorType); let ctorReturn: Type = unknownType; if (constraint) { const ctorSig = getSignaturesOfType(constraint, SignatureKind.Construct); if (ctorSig[0]) { ctorReturn = getReturnTypeOfSignature(ctorSig[0]); } } if (baseConstructorType.symbol.declarations) { addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn))); } } return type.resolvedBaseConstructorType ??= errorType; } type.resolvedBaseConstructorType ??= baseConstructorType; } return type.resolvedBaseConstructorType; } function getImplementsTypes(type: InterfaceType): BaseType[] { let resolvedImplementsTypes: BaseType[] = emptyArray; if (type.symbol.declarations) { for (const declaration of type.symbol.declarations) { const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration); if (!implementsTypeNodes) continue; for (const node of implementsTypeNodes) { const implementsType = getTypeFromTypeNode(node); if (!isErrorType(implementsType)) { if (resolvedImplementsTypes === emptyArray) { resolvedImplementsTypes = [implementsType as ObjectType]; } else { resolvedImplementsTypes.push(implementsType); } } } } } return resolvedImplementsTypes; } function reportCircularBaseType(node: Node, type: Type) { error(node, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); } function getBaseTypes(type: InterfaceType): BaseType[] { if (!type.baseTypesResolved) { if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) { if (type.objectFlags & ObjectFlags.Tuple) { type.resolvedBaseTypes = [getTupleBaseType(type as TupleType)]; } else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { if (type.symbol.flags & SymbolFlags.Class) { resolveBaseTypesOfClass(type); } if (type.symbol.flags & SymbolFlags.Interface) { resolveBaseTypesOfInterface(type); } } else { Debug.fail("type must be class or interface"); } if (!popTypeResolution() && type.symbol.declarations) { for (const declaration of type.symbol.declarations) { if (declaration.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.InterfaceDeclaration) { reportCircularBaseType(declaration, type); } } } } type.baseTypesResolved = true; } return type.resolvedBaseTypes; } function getTupleBaseType(type: TupleType) { const elementTypes = sameMap(type.typeParameters, (t, i) => type.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); return createArrayType(getUnionType(elementTypes || emptyArray), type.readonly); } function resolveBaseTypesOfClass(type: InterfaceType) { type.resolvedBaseTypes = resolvingEmptyArray; const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { return type.resolvedBaseTypes = emptyArray; } const baseTypeNode = getBaseTypeNodeOfClass(type)!; let baseType: Type; const originalBaseType = baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined; if ( baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class && areAllOuterTypeParametersApplied(originalBaseType!) ) { // When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the // class and all return the instance type of the class. There is no need for further checks and we can apply the // type arguments in the same manner as a type reference to get the same error reporting experience. baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol); } else if (baseConstructorType.flags & TypeFlags.Any) { baseType = baseConstructorType; } else { // The class derives from a "class-like" constructor function, check that we have at least one construct signature // with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere // we check that all instantiated signatures return the same type. const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode); if (!constructors.length) { error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments); return type.resolvedBaseTypes = emptyArray; } baseType = getReturnTypeOfSignature(constructors[0]); } if (isErrorType(baseType)) { return type.resolvedBaseTypes = emptyArray; } const reducedBaseType = getReducedType(baseType); if (!isValidBaseType(reducedBaseType)) { const elaboration = elaborateNeverIntersection(/*errorInfo*/ undefined, baseType); const diagnostic = chainDiagnosticMessages(elaboration, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(reducedBaseType)); diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(baseTypeNode.expression), baseTypeNode.expression, diagnostic)); return type.resolvedBaseTypes = emptyArray; } if (type === reducedBaseType || hasBaseType(reducedBaseType, type)) { error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); return type.resolvedBaseTypes = emptyArray; } if (type.resolvedBaseTypes === resolvingEmptyArray) { // Circular reference, likely through instantiation of default parameters // (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset // as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a // partial instantiation of the members without the base types fully resolved type.members = undefined; } return type.resolvedBaseTypes = [reducedBaseType]; } function areAllOuterTypeParametersApplied(type: Type): boolean { // TODO: GH#18217 Shouldn't this take an InterfaceType? // An unapplied type parameter has its symbol still the same as the matching argument symbol. // Since parameters are applied outer-to-inner, only the last outer parameter needs to be checked. const outerTypeParameters = (type as InterfaceType).outerTypeParameters; if (outerTypeParameters) { const last = outerTypeParameters.length - 1; const typeArguments = getTypeArguments(type as TypeReference); return outerTypeParameters[last].symbol !== typeArguments[last].symbol; } return true; } // A valid base type is `any`, an object type or intersection of object types. function isValidBaseType(type: Type): type is BaseType { if (type.flags & TypeFlags.TypeParameter) { const constraint = getBaseConstraintOfType(type); if (constraint) { return isValidBaseType(constraint); } } // TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed? // There's no reason a `T` should be allowed while a `Readonly` should not. return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isValidBaseType)); } function resolveBaseTypesOfInterface(type: InterfaceType): void { type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; if (type.symbol.declarations) { for (const declaration of type.symbol.declarations) { if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)) { for (const node of getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)!) { const baseType = getReducedType(getTypeFromTypeNode(node)); if (!isErrorType(baseType)) { if (isValidBaseType(baseType)) { if (type !== baseType && !hasBaseType(baseType, type)) { if (type.resolvedBaseTypes === emptyArray) { type.resolvedBaseTypes = [baseType as ObjectType]; } else { type.resolvedBaseTypes.push(baseType); } } else { reportCircularBaseType(declaration, type); } } else { error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members); } } } } } } } /** * Returns true if the interface given by the symbol is free of "this" references. * * Specifically, the result is true if the interface itself contains no references * to "this" in its body, if all base types are interfaces, * and if none of the base interfaces have a "this" type. */ function isThislessInterface(symbol: Symbol): boolean { if (!symbol.declarations) { return true; } for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.InterfaceDeclaration) { if (declaration.flags & NodeFlags.ContainsThis) { return false; } const baseTypeNodes = getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration); if (baseTypeNodes) { for (const node of baseTypeNodes) { if (isEntityNameExpression(node.expression)) { const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) { return false; } } } } } } return true; } function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType { let links = getSymbolLinks(symbol); const originalLinks = links; if (!links.declaredType) { const kind = symbol.flags & SymbolFlags.Class ? ObjectFlags.Class : ObjectFlags.Interface; const merged = mergeJSSymbols(symbol, symbol.valueDeclaration && getAssignedClassSymbol(symbol.valueDeclaration)); if (merged) { // note:we overwrite links because we just cloned the symbol symbol = merged; links = merged.links; } const type = originalLinks.declaredType = links.declaredType = createObjectType(kind, symbol) as InterfaceType; const outerTypeParameters = getOuterTypeParametersOfClassOrInterface(symbol); const localTypeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); // A class or interface is generic if it has type parameters or a "this" type. We always give classes a "this" type // because it is not feasible to analyze all members to determine if the "this" type escapes the class (in particular, // property types inferred from initializers and method return types inferred from return statements are very hard // to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of // "this" references. if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) { type.objectFlags |= ObjectFlags.Reference; type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; type.localTypeParameters = localTypeParameters; (type as GenericType).instantiations = new Map(); (type as GenericType).instantiations.set(getTypeListId(type.typeParameters), type as GenericType); (type as GenericType).target = type as GenericType; (type as GenericType).resolvedTypeArguments = type.typeParameters; type.thisType = createTypeParameter(symbol); type.thisType.isThisType = true; type.thisType.constraint = type; } } return links.declaredType as InterfaceType; } function getDeclaredTypeOfTypeAlias(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.declaredType) { // Note that we use the links object as the target here because the symbol object is used as the unique // identity for resolution of the 'type' property in SymbolLinks. if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) { return errorType; } const declaration = Debug.checkDefined(symbol.declarations?.find(isTypeAlias), "Type alias symbol with no valid declaration found"); const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; // If typeNode is missing, we will error in checkJSDocTypedefTag. let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType; if (popTypeResolution()) { const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); if (typeParameters) { // Initialize the instantiation cache for generic type aliases. The declared type corresponds to // an instantiation of the type alias with the type parameters supplied as type arguments. links.typeParameters = typeParameters; links.instantiations = new Map(); links.instantiations.set(getTypeListId(typeParameters), type); } if (type === intrinsicMarkerType && symbol.escapedName === "BuiltinIteratorReturn") { type = getBuiltinIteratorReturnType(); } } else { type = errorType; if (declaration.kind === SyntaxKind.JSDocEnumTag) { error(declaration.typeExpression.type, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } else { error(isNamedDeclaration(declaration) ? declaration.name || declaration : declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } } links.declaredType ??= type; } return links.declaredType; } function getBaseTypeOfEnumLikeType(type: Type) { return type.flags & TypeFlags.EnumLike && type.symbol.flags & SymbolFlags.EnumMember ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type; } function getDeclaredTypeOfEnum(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.declaredType) { const memberTypeList: Type[] = []; if (symbol.declarations) { for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { for (const member of (declaration as EnumDeclaration).members) { if (hasBindableName(member)) { const memberSymbol = getSymbolOfDeclaration(member); const value = getEnumMemberValue(member).value; const memberType = getFreshTypeOfLiteralType( value !== undefined ? getEnumLiteralType(value, getSymbolId(symbol), memberSymbol) : createComputedEnumType(memberSymbol), ); getSymbolLinks(memberSymbol).declaredType = memberType; memberTypeList.push(getRegularTypeOfLiteralType(memberType)); } } } } } const enumType = memberTypeList.length ? getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined) : createComputedEnumType(symbol); if (enumType.flags & TypeFlags.Union) { enumType.flags |= TypeFlags.EnumLiteral; enumType.symbol = symbol; } links.declaredType = enumType; } return links.declaredType; } function createComputedEnumType(symbol: Symbol) { const regularType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType; const freshType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType; regularType.regularType = regularType; regularType.freshType = freshType; freshType.regularType = regularType; freshType.freshType = freshType; return regularType; } function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.declaredType) { const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); if (!links.declaredType) { links.declaredType = enumType; } } return links.declaredType; } function getDeclaredTypeOfTypeParameter(symbol: Symbol): TypeParameter { const links = getSymbolLinks(symbol); return links.declaredType || (links.declaredType = createTypeParameter(symbol)); } function getDeclaredTypeOfAlias(symbol: Symbol): Type { const links = getSymbolLinks(symbol); return links.declaredType || (links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol))); } function getDeclaredTypeOfSymbol(symbol: Symbol): Type { return tryGetDeclaredTypeOfSymbol(symbol) || errorType; } function tryGetDeclaredTypeOfSymbol(symbol: Symbol): Type | undefined { if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { return getDeclaredTypeOfClassOrInterface(symbol); } if (symbol.flags & SymbolFlags.TypeAlias) { return getDeclaredTypeOfTypeAlias(symbol); } if (symbol.flags & SymbolFlags.TypeParameter) { return getDeclaredTypeOfTypeParameter(symbol); } if (symbol.flags & SymbolFlags.Enum) { return getDeclaredTypeOfEnum(symbol); } if (symbol.flags & SymbolFlags.EnumMember) { return getDeclaredTypeOfEnumMember(symbol); } if (symbol.flags & SymbolFlags.Alias) { return getDeclaredTypeOfAlias(symbol); } return undefined; } /** * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string * literal type, an array with an element type that is free of this references, or a type reference that is * free of this references. */ function isThislessType(node: TypeNode): boolean { switch (node.kind) { case SyntaxKind.AnyKeyword: case SyntaxKind.UnknownKeyword: case SyntaxKind.StringKeyword: case SyntaxKind.NumberKeyword: case SyntaxKind.BigIntKeyword: case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.ObjectKeyword: case SyntaxKind.VoidKeyword: case SyntaxKind.UndefinedKeyword: case SyntaxKind.NeverKeyword: case SyntaxKind.LiteralType: return true; case SyntaxKind.ArrayType: return isThislessType((node as ArrayTypeNode).elementType); case SyntaxKind.TypeReference: return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments!.every(isThislessType); } return false; } /** A type parameter is thisless if its constraint is thisless, or if it has no constraint. */ function isThislessTypeParameter(node: TypeParameterDeclaration) { const constraint = getEffectiveConstraintOfTypeParameter(node); return !constraint || isThislessType(constraint); } /** * A variable-like declaration is free of this references if it has a type annotation * that is thisless, or if it has no type annotation and no initializer (and is thus of type any). */ function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { const typeNode = getEffectiveTypeAnnotationNode(node); return typeNode ? isThislessType(typeNode) : !hasInitializer(node); } /** * A function-like declaration is considered free of `this` references if it has a return type * annotation that is free of this references and if each parameter is thisless and if * each type parameter (if present) is thisless. */ function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { const returnType = getEffectiveReturnTypeNode(node); const typeParameters = getEffectiveTypeParameterDeclarations(node); return (node.kind === SyntaxKind.Constructor || (!!returnType && isThislessType(returnType))) && node.parameters.every(isThislessVariableLikeDeclaration) && typeParameters.every(isThislessTypeParameter); } /** * Returns true if the class or interface member given by the symbol is free of "this" references. The * function may return false for symbols that are actually free of "this" references because it is not * feasible to perform a complete analysis in all cases. In particular, property members with types * inferred from their initializers and function members with inferred return types are conservatively * assumed not to be free of "this" references. */ function isThisless(symbol: Symbol): boolean { if (symbol.declarations && symbol.declarations.length === 1) { const declaration = symbol.declarations[0]; if (declaration) { switch (declaration.kind) { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: return isThislessVariableLikeDeclaration(declaration as VariableLikeDeclaration); case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return isThislessFunctionLikeDeclaration(declaration as FunctionLikeDeclaration | AccessorDeclaration); } } } return false; } // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { const result = createSymbolTable(); for (const symbol of symbols) { result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper)); } return result; } function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { for (const base of baseSymbols) { if (isStaticPrivateIdentifierProperty(base)) { continue; } const derived = symbols.get(base.escapedName); if ( !derived // non-constructor/static-block assignment declarations are ignored here; they're not treated as overrides || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration) && !isConstructorDeclaredProperty(derived) && !getContainingClassStaticBlock(derived.valueDeclaration) ) { symbols.set(base.escapedName, base); symbols.set(base.escapedName, base); } } } function isStaticPrivateIdentifierProperty(s: Symbol): boolean { return !!s.valueDeclaration && isPrivateIdentifierClassElementDeclaration(s.valueDeclaration) && isStatic(s.valueDeclaration); } function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers { if (!(type as InterfaceTypeWithDeclaredMembers).declaredProperties) { const symbol = type.symbol; const members = getMembersOfSymbol(symbol); (type as InterfaceTypeWithDeclaredMembers).declaredProperties = getNamedMembers(members); // Start with signatures at empty array in case of recursive types (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = emptyArray; (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = emptyArray; (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = emptyArray; (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = getIndexInfosOfSymbol(symbol); } return type as InterfaceTypeWithDeclaredMembers; } /** * Indicates whether a declaration name is definitely late-bindable. * A declaration name is only late-bindable if: * - It is a `ComputedPropertyName`. * - Its expression is an `Identifier` or either a `PropertyAccessExpression` an * `ElementAccessExpression` consisting only of these same three types of nodes. * - The type of its expression is a string or numeric literal type, or is a `unique symbol` type. */ function isLateBindableName(node: DeclarationName): node is LateBoundName { return isLateBindableAST(node) && isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached((node as ElementAccessExpression).argumentExpression)); } function isLateBindableIndexSignature(node: DeclarationName): node is LateBoundName { return isLateBindableAST(node) && isTypeUsableAsIndexSignature(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached((node as ElementAccessExpression).argumentExpression)); } function isLateBindableAST(node: DeclarationName) { if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) { return false; } const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; return isEntityNameExpression(expr); } function isTypeUsableAsIndexSignature(type: Type): boolean { return isTypeAssignableTo(type, stringNumberSymbolType); } function isLateBoundName(name: __String): boolean { return (name as string).charCodeAt(0) === CharacterCodes._ && (name as string).charCodeAt(1) === CharacterCodes._ && (name as string).charCodeAt(2) === CharacterCodes.at; } /** * Indicates whether a declaration has a late-bindable dynamic name. */ function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | LateBoundBinaryExpressionDeclaration { const name = getNameOfDeclaration(node); return !!name && isLateBindableName(name); } function hasLateBindableIndexSignature(node: Declaration) { const name = getNameOfDeclaration(node); return !!name && isLateBindableIndexSignature(name); } /** * Indicates whether a declaration has an early-bound name or a dynamic name that can be late-bound. */ function hasBindableName(node: Declaration) { return !hasDynamicName(node) || hasLateBindableName(node); } /** * Indicates whether a declaration name is a dynamic name that cannot be late-bound. */ function isNonBindableDynamicName(node: DeclarationName) { return isDynamicName(node) && !isLateBindableName(node); } /** * Adds a declaration to a late-bound dynamic member. This performs the same function for * late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound * members. */ function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) { Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol."); symbol.flags |= symbolFlags; getSymbolLinks(member.symbol).lateSymbol = symbol; if (!symbol.declarations) { symbol.declarations = [member]; } else if (!member.symbol.isReplaceableByMethod) { symbol.declarations.push(member); } if (symbolFlags & SymbolFlags.Value) { setValueDeclaration(symbol, member); } } /** * Performs late-binding of a dynamic member. This performs the same function for * late-bound members that `declareSymbol` in binder.ts performs for early-bound * members. * * If a symbol is a dynamic name from a computed property, we perform an additional "late" * binding phase to attempt to resolve the name for the symbol from the type of the computed * property's expression. If the type of the expression is a string-literal, numeric-literal, * or unique symbol type, we can use that type as the name of the symbol. * * For example, given: * * const x = Symbol(); * * interface I { * [x]: number; * } * * The binder gives the property `[x]: number` a special symbol with the name "__computed". * In the late-binding phase we can type-check the expression `x` and see that it has a * unique symbol type which we can then use as the name of the member. This allows users * to define custom symbols that can be used in the members of an object type. * * @param parent The containing symbol for the member. * @param earlySymbols The early-bound symbols of the parent. * @param lateSymbols The late-bound symbols of the parent. * @param decl The member to bind. */ function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: Map<__string transientsymbol>, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) { Debug.assert(!!decl.symbol, "The member is expected to have a symbol."); const links = getNodeLinks(decl); if (!links.resolvedSymbol) { // In the event we attempt to resolve the late-bound name of this member recursively, // fall back to the early-bound name of this member. links.resolvedSymbol = decl.symbol; const declName = isBinaryExpression(decl) ? decl.left : decl.name; const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName); if (isTypeUsableAsPropertyName(type)) { const memberName = getPropertyNameFromType(type); const symbolFlags = decl.symbol.flags; // Get or add a late-bound symbol for the member. This allows us to merge late-bound accessor declarations. let lateSymbol = lateSymbols.get(memberName); if (!lateSymbol) lateSymbols.set(memberName, lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late)); // Report an error if there's a symbol declaration with the same name and conflicting flags. const earlySymbol = earlySymbols && earlySymbols.get(memberName); // Duplicate property declarations of classes are checked in checkClassForDuplicateDeclarations. if (!(parent.flags & SymbolFlags.Class) && lateSymbol.flags & getExcludedSymbolFlags(symbolFlags)) { // If we have an existing early-bound member, combine its declarations so that we can // report an error at each declaration. const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations; const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName); forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name)); error(declName || decl, Diagnostics.Duplicate_property_0, name); lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late); } lateSymbol.links.nameType = type; addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags); if (lateSymbol.parent) { Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one"); } else { lateSymbol.parent = parent; } return links.resolvedSymbol = lateSymbol; } } return links.resolvedSymbol; } function lateBindIndexSignature(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: Map<__string transientsymbol>, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) { // First, late bind the index symbol itself, if needed let indexSymbol = lateSymbols.get(InternalSymbolName.Index); if (!indexSymbol) { const early = earlySymbols?.get(InternalSymbolName.Index); if (!early) { indexSymbol = createSymbol(SymbolFlags.None, InternalSymbolName.Index, CheckFlags.Late); } else { indexSymbol = cloneSymbol(early); indexSymbol.links.checkFlags |= CheckFlags.Late; } lateSymbols.set(InternalSymbolName.Index, indexSymbol); } // Then just add the computed name as a late bound declaration // (note: unlike `addDeclarationToLateBoundSymbol` we do not set up a `.lateSymbol` on `decl`'s links, // since that would point at an index symbol and not a single property symbol, like most consumers would expect) if (!indexSymbol.declarations) { indexSymbol.declarations = [decl]; } else if (!decl.symbol.isReplaceableByMethod) { indexSymbol.declarations.push(decl); } } function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): Map<__string symbol> { const links = getSymbolLinks(symbol); if (!links[resolutionKind]) { const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; const earlySymbols = !isStatic ? symbol.members : symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol).exports : symbol.exports; // In the event we recursively resolve the members/exports of the symbol, we // set the initial value of resolvedMembers/resolvedExports to the early-bound // members/exports of the symbol. links[resolutionKind] = earlySymbols || emptySymbols; // fill in any as-yet-unresolved late-bound members. const lateSymbols = createSymbolTable() as Map<__string transientsymbol>; for (const decl of symbol.declarations || emptyArray) { const members = getMembersOfDeclaration(decl); if (members) { for (const member of members) { if (isStatic === hasStaticModifier(member)) { if (hasLateBindableName(member)) { lateBindMember(symbol, earlySymbols, lateSymbols, member); } else if (hasLateBindableIndexSignature(member)) { lateBindIndexSignature(symbol, earlySymbols, lateSymbols, member as Node as LateBoundDeclaration | LateBoundBinaryExpressionDeclaration); } } } } } const assignments = getFunctionExpressionParentSymbolOrSymbol(symbol).assignmentDeclarationMembers; if (assignments) { const decls = arrayFrom(assignments.values()); for (const member of decls) { const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression); const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty || isBinaryExpression(member) && isPossiblyAliasedThisProperty(member, assignmentKind) || assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty || assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name if (isStatic === !isInstanceMember) { if (hasLateBindableName(member)) { lateBindMember(symbol, earlySymbols, lateSymbols, member); } } } } let resolved = combineSymbolTables(earlySymbols, lateSymbols); if (symbol.flags & SymbolFlags.Transient && links.cjsExportMerged && symbol.declarations) { for (const decl of symbol.declarations) { const original = getSymbolLinks(decl.symbol)[resolutionKind]; if (!resolved) { resolved = original; continue; } if (!original) continue; original.forEach((s, name) => { const existing = resolved!.get(name); if (!existing) resolved!.set(name, s); else if (existing === s) return; else resolved!.set(name, mergeSymbol(existing, s)); }); } } links[resolutionKind] = resolved || emptySymbols; } return links[resolutionKind]; } /** * Gets a SymbolTable containing both the early- and late-bound members of a symbol. * * For a description of late-binding, see `lateBindMember`. */ function getMembersOfSymbol(symbol: Symbol) { return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedMembers) : symbol.members || emptySymbols; } /** * If a symbol is the dynamic name of the member of an object type, get the late-bound * symbol of the member. * * For a description of late-binding, see `lateBindMember`. */ function getLateBoundSymbol(symbol: Symbol): Symbol { if (symbol.flags & SymbolFlags.ClassMember && symbol.escapedName === InternalSymbolName.Computed) { const links = getSymbolLinks(symbol); if (!links.lateSymbol && some(symbol.declarations, hasLateBindableName)) { // force late binding of members/exports. This will set the late-bound symbol const parent = getMergedSymbol(symbol.parent)!; if (some(symbol.declarations, hasStaticModifier)) { getExportsOfSymbol(parent); } else { getMembersOfSymbol(parent); } } return links.lateSymbol || (links.lateSymbol = symbol); } return symbol; } function getTypeWithThisArgument(type: Type, thisArgument?: Type, needApparentType?: boolean): Type { if (getObjectFlags(type) & ObjectFlags.Reference) { const target = (type as TypeReference).target; const typeArguments = getTypeArguments(type as TypeReference); return length(target.typeParameters) === length(typeArguments) ? createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])) : type; } else if (type.flags & TypeFlags.Intersection) { const types = sameMap((type as IntersectionType).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType)); return types !== (type as IntersectionType).types ? getIntersectionType(types) : type; } return needApparentType ? getApparentType(type) : type; } function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly Type[]) { let mapper: TypeMapper | undefined; let members: SymbolTable; let callSignatures: readonly Signature[]; let constructSignatures: readonly Signature[]; let indexInfos: readonly IndexInfo[]; if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { members = source.symbol ? getMembersOfSymbol(source.symbol) : createSymbolTable(source.declaredProperties); callSignatures = source.declaredCallSignatures; constructSignatures = source.declaredConstructSignatures; indexInfos = source.declaredIndexInfos; } else { mapper = createTypeMapper(typeParameters, typeArguments); members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper); constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper); indexInfos = instantiateIndexInfos(source.declaredIndexInfos, mapper); } const baseTypes = getBaseTypes(source); if (baseTypes.length) { if (source.symbol && members === getMembersOfSymbol(source.symbol)) { const symbolTable = createSymbolTable(source.declaredProperties); // copy index signature symbol as well (for quickinfo) const sourceIndex = getIndexSymbol(source.symbol); if (sourceIndex) { symbolTable.set(InternalSymbolName.Index, sourceIndex); } members = symbolTable; } setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); const thisArgument = lastOrUndefined(typeArguments); for (const baseType of baseTypes) { const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType; addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType)); callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct)); const inheritedIndexInfos = instantiatedBaseType !== anyType ? getIndexInfosOfType(instantiatedBaseType) : [anyBaseTypeIndexInfo]; indexInfos = concatenate(indexInfos, filter(inheritedIndexInfos, info => !findIndexInfo(indexInfos, info.keyType))); } } setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); } function resolveClassOrInterfaceMembers(type: InterfaceType): void { resolveObjectTypeMembers(type, resolveDeclaredMembers(type), emptyArray, emptyArray); } function resolveTypeReferenceMembers(type: TypeReference): void { const source = resolveDeclaredMembers(type.target); const typeParameters = concatenate(source.typeParameters!, [source.thisType!]); const typeArguments = getTypeArguments(type); const paddedTypeArguments = typeArguments.length === typeParameters.length ? typeArguments : concatenate(typeArguments, [type]); resolveObjectTypeMembers(type, source, typeParameters, paddedTypeArguments); } function createSignature( declaration: SignatureDeclaration | JSDocSignature | undefined, typeParameters: readonly TypeParameter[] | undefined, thisParameter: Symbol | undefined, parameters: readonly Symbol[], resolvedReturnType: Type | undefined, resolvedTypePredicate: TypePredicate | undefined, minArgumentCount: number, flags: SignatureFlags, ): Signature { const sig = new Signature(checker, flags); sig.declaration = declaration; sig.typeParameters = typeParameters; sig.parameters = parameters; sig.thisParameter = thisParameter; sig.resolvedReturnType = resolvedReturnType; sig.resolvedTypePredicate = resolvedTypePredicate; sig.minArgumentCount = minArgumentCount; sig.resolvedMinArgumentCount = undefined; sig.target = undefined; sig.mapper = undefined; sig.compositeSignatures = undefined; sig.compositeKind = undefined; return sig; } function cloneSignature(sig: Signature): Signature { const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags); result.target = sig.target; result.mapper = sig.mapper; result.compositeSignatures = sig.compositeSignatures; result.compositeKind = sig.compositeKind; return result; } function createUnionSignature(signature: Signature, unionSignatures: Signature[]) { const result = cloneSignature(signature); result.compositeSignatures = unionSignatures; result.compositeKind = TypeFlags.Union; result.target = undefined; result.mapper = undefined; return result; } function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature { if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) { return signature; } if (!signature.optionalCallSignatureCache) { signature.optionalCallSignatureCache = {}; } const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer"; return signature.optionalCallSignatureCache[key] || (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags)); } function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) { Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain, "An optional call signature can either be for an inner call chain or an outer call chain, but not both."); const result = cloneSignature(signature); result.flags |= callChainFlags; return result; } function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] { if (signatureHasRestParameter(sig)) { const restIndex = sig.parameters.length - 1; const restSymbol = sig.parameters[restIndex]; const restType = getTypeOfSymbol(restSymbol); if (isTupleType(restType)) { return [expandSignatureParametersWithTupleMembers(restType, restIndex, restSymbol)]; } else if (!skipUnionExpanding && restType.flags & TypeFlags.Union && every((restType as UnionType).types, isTupleType)) { return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex, restSymbol)); } } return [sig.parameters]; function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restSymbol: Symbol) { const elementTypes = getTypeArguments(restType); const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restSymbol); const restParams = map(elementTypes, (t, i) => { // Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name const name = associatedNames && associatedNames[i] ? associatedNames[i] : getParameterNameAtPosition(sig, restIndex + i, restType); const flags = restType.target.elementFlags[i]; const checkFlags = flags & ElementFlags.Variable ? CheckFlags.RestParameter : flags & ElementFlags.Optional ? CheckFlags.OptionalParameter : 0; const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags); symbol.links.type = flags & ElementFlags.Rest ? createArrayType(t) : t; return symbol; }); return concatenate(sig.parameters.slice(0, restIndex), restParams); } function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restSymbol: Symbol) { const names = map(type.target.labeledElementDeclarations, (labeledElement, i) => getTupleElementLabel(labeledElement, i, type.target.elementFlags[i], restSymbol)); if (names) { const duplicates: number[] = []; const uniqueNames = new Set<__string>(); for (let i = 0; i (); for (const i of duplicates) { let counter = counters.get(names[i]) ?? 1; let name: __String; while (!tryAddToSet(uniqueNames, name = `${names[i]}_${counter}` as __String)) { counter++; } names[i] = name; counters.set(names[i], counter + 1); } } return names; } } function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { const baseConstructorType = getBaseConstructorTypeOfClass(classType); const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); const declaration = getClassLikeDeclarationOfSymbol(classType.symbol); const isAbstract = !!declaration && hasSyntacticModifier(declaration, ModifierFlags.Abstract); if (baseSignatures.length === 0) { return [createSignature(/*declaration*/ undefined, classType.localTypeParameters, /*thisParameter*/ undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, isAbstract ? SignatureFlags.Abstract : SignatureFlags.None)]; } const baseTypeNode = getBaseTypeNodeOfClass(classType)!; const isJavaScript = isInJSFile(baseTypeNode); const typeArguments = typeArgumentsFromTypeReferenceNode(baseTypeNode); const typeArgCount = length(typeArguments); const result: Signature[] = []; for (const baseSig of baseSignatures) { const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters); const typeParamCount = length(baseSig.typeParameters); if (isJavaScript || typeArgCount >= minTypeArgumentCount && typeArgCount 0) { return undefined; } for (let i = 1; i 1) { indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets } for (const signature of signatureLists[i]) { // Only process signatures with parameter lists that aren't already in the result list if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true)) { const unionSignatures = findMatchingSignatures(signatureLists, signature, i); if (unionSignatures) { let s = signature; // Union the result types when more than one signature matches if (unionSignatures.length > 1) { let thisParameter = signature.thisParameter; const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter); if (firstThisParameterOfUnionSignatures) { const thisType = getIntersectionType(mapDefined(unionSignatures, sig => sig.thisParameter && getTypeOfSymbol(sig.thisParameter))); thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType); } s = createUnionSignature(signature, unionSignatures); s.thisParameter = thisParameter; } (result || (result = [])).push(s); } } } } if (!length(result) && indexWithLengthOverOne !== -1) { // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single // signature that handles all over them. We only do this when there are overloads in only one constituent. // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of // signatures from the type, whose ordering would be non-obvious) const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0]; let results: Signature[] | undefined = masterList.slice(); for (const signatures of signatureLists) { if (signatures !== masterList) { const signature = signatures[0]; Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass"); results = !!signature.typeParameters && some(results, s => !!s.typeParameters && !compareTypeParametersIdentical(signature.typeParameters, s.typeParameters)) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); if (!results) { break; } } } result = results; } return result || emptyArray; } function compareTypeParametersIdentical(sourceParams: readonly TypeParameter[] | undefined, targetParams: readonly TypeParameter[] | undefined): boolean { if (length(sourceParams) !== length(targetParams)) { return false; } if (!sourceParams || !targetParams) { return true; } const mapper = createTypeMapper(targetParams, sourceParams); for (let i = 0; i ` as the same as `` if (!isTypeIdenticalTo(getConstraintFromTypeParameter(source) || unknownType, instantiateType(getConstraintFromTypeParameter(target) || unknownType, mapper))) return false; // We don't compare defaults - we just use the type parameter defaults from the first signature that seems to match. // It might make sense to combine these defaults in the future, but doing so intelligently requires knowing // if the parameter is used covariantly or contravariantly (so we intersect if it's used like a parameter or union if used like a return type) // and, since it's just an inference _default_, just picking one arbitrarily works OK. } return true; } function combineUnionThisParam(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 // permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures. const thisType = getIntersectionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]); return createSymbolWithType(left, thisType); } function combineUnionParameters(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 = 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, isRestParam ? CheckFlags.RestParameter : isOptional ? CheckFlags.OptionalParameter : 0, ); paramSymbol.links.type = isRestParam ? createArrayType(unionParamType) : unionParamType; params[i] = paramSymbol; } if (needsExtraRestElement) { const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String, CheckFlags.RestParameter); restParamSymbol.links.type = createArrayType(getTypeAtPosition(shorter, longestCount)); if (shorter === right) { restParamSymbol.links.type = instantiateType(restParamSymbol.links.type, mapper); } params[longestCount] = restParamSymbol; } return params; } function combineSignaturesOfUnionMembers(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 } let flags = (left.flags | right.flags) & (SignatureFlags.PropagatingFlags & ~SignatureFlags.HasRestParameter); const declaration = left.declaration; const params = combineUnionParameters(left, right, paramMapper); const lastParam = lastOrUndefined(params); if (lastParam && getCheckFlags(lastParam) & CheckFlags.RestParameter) { flags |= SignatureFlags.HasRestParameter; } const thisParam = combineUnionThisParam(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, flags, ); result.compositeKind = TypeFlags.Union; 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; } else if (left.compositeKind !== TypeFlags.Intersection && left.mapper && left.compositeSignatures) { result.mapper = left.mapper; } return result; } function getUnionIndexInfos(types: readonly Type[]): IndexInfo[] { const sourceInfos = getIndexInfosOfType(types[0]); if (sourceInfos) { const result = []; for (const info of sourceInfos) { const indexType = info.keyType; if (every(types, t => !!getIndexInfoOfType(t, indexType))) { result.push(createIndexInfo(indexType, getUnionType(map(types, t => getIndexTypeOfType(t, indexType)!)), some(types, t => getIndexInfoOfType(t, indexType)!.isReadonly))); } } return result; } return emptyArray; } function resolveUnionTypeMembers(type: UnionType) { // The members and properties collections are empty for union types. To get all properties of a union // type use getPropertiesOfType (only the language service uses this). const callSignatures = getUnionSignatures(map(type.types, t => t === globalFunctionType ? [unknownSignature] : getSignaturesOfType(t, SignatureKind.Call))); const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct))); const indexInfos = getUnionIndexInfos(type.types); setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, indexInfos); } function intersectTypes(type1: Type, type2: Type): Type; function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined; function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined { return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]); } function findMixins(types: readonly Type[]): readonly boolean[] { const constructorTypeCount = countWhere(types, t => getSignaturesOfType(t, SignatureKind.Construct).length > 0); const mixinFlags = map(types, isMixinConstructorType); if (constructorTypeCount > 0 && constructorTypeCount === countWhere(mixinFlags, b => b)) { const firstMixinIndex = mixinFlags.indexOf(/*searchElement*/ true); mixinFlags[firstMixinIndex] = false; } return mixinFlags; } function includeMixinType(type: Type, types: readonly Type[], mixinFlags: readonly boolean[], index: number): Type { const mixedTypes: Type[] = []; for (let i = 0; i b); for (let i = 0; i A } & { new(s: string) => B }' has a single construct signature // 'new(s: string) => A & B'. if (!mixinFlags[i]) { let signatures = getSignaturesOfType(t, SignatureKind.Construct); if (signatures.length && mixinCount > 0) { signatures = map(signatures, s => { const clone = cloneSignature(s); clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, mixinFlags, i); return clone; }); } constructSignatures = appendSignatures(constructSignatures, signatures); } callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); indexInfos = reduceLeft(getIndexInfosOfType(t), (infos, newInfo) => appendIndexInfo(infos, newInfo, /*union*/ false), indexInfos); } setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, indexInfos || emptyArray); } function appendSignatures(signatures: Signature[] | undefined, newSignatures: readonly Signature[]) { for (const sig of newSignatures) { if (!signatures || every(signatures, s => !compareSignaturesIdentical(s, sig, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, compareTypesIdentical))) { signatures = append(signatures, sig); } } return signatures; } function appendIndexInfo(indexInfos: IndexInfo[] | undefined, newInfo: IndexInfo, union: boolean) { if (indexInfos) { for (let i = 0; i (); members.forEach(p => { if (!(p.flags & SymbolFlags.BlockScoped) && !(p.flags & SymbolFlags.ValueModule && p.declarations?.length && every(p.declarations, isAmbientModule))) { varsOnly.set(p.escapedName, p); } }); members = varsOnly; } let baseConstructorIndexInfo: IndexInfo | undefined; setStructuredTypeMembers(type, members, emptyArray, emptyArray, emptyArray); if (symbol.flags & SymbolFlags.Class) { const classType = getDeclaredTypeOfClassOrInterface(symbol); const baseConstructorType = getBaseConstructorTypeOfClass(classType); if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { members = createSymbolTable(getNamedOrIndexSignatureMembers(members)); addInheritedMembers(members, getPropertiesOfType(baseConstructorType)); } else if (baseConstructorType === anyType) { baseConstructorIndexInfo = anyBaseTypeIndexInfo; } } const indexSymbol = getIndexSymbolFromSymbolTable(members); if (indexSymbol) { indexInfos = getIndexInfosOfIndexSymbol(indexSymbol, arrayFrom(members.values())); } else { if (baseConstructorIndexInfo) { indexInfos = append(indexInfos, baseConstructorIndexInfo); } if ( symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike))) ) { indexInfos = append(indexInfos, enumNumberIndexInfo); } } setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); // We resolve the members before computing the signatures because a signature may use // typeof with a qualified name expression that circularly references the type we are // in the process of resolving (see issue #6072). The temporarily empty signature list // will never be observed because a qualified name can't reference signatures. if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { type.callSignatures = getSignaturesOfSymbol(symbol); } // And likewise for construct signatures for classes if (symbol.flags & SymbolFlags.Class) { const classType = getDeclaredTypeOfClassOrInterface(symbol); let constructSignatures = symbol.members ? getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)) : emptyArray; if (symbol.flags & SymbolFlags.Function) { constructSignatures = addRange( constructSignatures.slice(), mapDefined( type.callSignatures, sig => isJSConstructor(sig.declaration) ? createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) : undefined, ), ); } if (!constructSignatures.length) { constructSignatures = getDefaultConstructSignatures(classType); } type.constructSignatures = constructSignatures; } } type ReplaceableIndexedAccessType = IndexedAccessType & { objectType: TypeParameter; indexType: TypeParameter; }; function replaceIndexedAccess(instantiable: Type, type: ReplaceableIndexedAccessType, replacement: Type) { // map type.indexType to 0 // map type.objectType to `[TReplacement]` // thus making the indexed access `[TReplacement][0]` or `TReplacement` return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getNumberLiteralType(0), createTupleType([replacement])])); } // If the original mapped type had an intersection constraint we extract its components, // and we make an attempt to do so even if the intersection has been reduced to a union. // This entire process allows us to possibly retrieve the filtering type literals. // e.g. { [K in keyof U & ("a" | "b") ] } -> "a" | "b" function getLimitedConstraint(type: ReverseMappedType) { const constraint = getConstraintTypeFromMappedType(type.mappedType); if (!(constraint.flags & TypeFlags.Union || constraint.flags & TypeFlags.Intersection)) { return; } const origin = (constraint.flags & TypeFlags.Union) ? (constraint as UnionType).origin : (constraint as IntersectionType); if (!origin || !(origin.flags & TypeFlags.Intersection)) { return; } const limitedConstraint = getIntersectionType((origin as IntersectionType).types.filter(t => t !== type.constraintType)); return limitedConstraint !== neverType ? limitedConstraint : undefined; } function resolveReverseMappedTypeMembers(type: ReverseMappedType) { const indexInfo = getIndexInfoOfType(type.source, stringType); const modifiers = getMappedTypeModifiers(type.mappedType); const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true; const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType) || unknownType, readonlyMask && indexInfo.isReadonly)] : emptyArray; const members = createSymbolTable(); const limitedConstraint = getLimitedConstraint(type); for (const prop of getPropertiesOfType(type.source)) { // In case of a reverse mapped type with an intersection constraint, if we were able to // extract the filtering type literals we skip those properties that are not assignable to them, // because the extra properties wouldn't get through the application of the mapped type anyway if (limitedConstraint) { const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique); if (!isTypeAssignableTo(propertyNameType, limitedConstraint)) { continue; } } const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol; inferredProp.declarations = prop.declarations; inferredProp.links.nameType = getSymbolLinks(prop).nameType; inferredProp.links.propertyType = getTypeOfSymbol(prop); if ( type.constraintType.type.flags & TypeFlags.IndexedAccess && (type.constraintType.type as IndexedAccessType).objectType.flags & TypeFlags.TypeParameter && (type.constraintType.type as IndexedAccessType).indexType.flags & TypeFlags.TypeParameter ) { // A reverse mapping of `{[K in keyof T[K_1]]: T[K_1]}` is the same as that of `{[K in keyof T]: T}`, since all we care about is // inferring to the "type parameter" (or indexed access) shared by the constraint and template. So, to reduce the number of // type identities produced, we simplify such indexed access occurences const newTypeParam = (type.constraintType.type as IndexedAccessType).objectType; const newMappedType = replaceIndexedAccess(type.mappedType, type.constraintType.type as ReplaceableIndexedAccessType, newTypeParam); inferredProp.links.mappedType = newMappedType as MappedType; inferredProp.links.constraintType = getIndexType(newTypeParam) as IndexType; } else { inferredProp.links.mappedType = type.mappedType; inferredProp.links.constraintType = type.constraintType; } members.set(prop.escapedName, inferredProp); } setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); } // Return the lower bound of the key type in a mapped type. Intuitively, the lower // bound includes those keys that are known to always be present, for example because // because of constraints on type parameters (e.g. 'keyof T' for a constrained T). function getLowerBoundOfKeyType(type: Type): Type { if (type.flags & TypeFlags.Index) { const t = getApparentType((type as IndexType).type); return isGenericTupleType(t) ? getKnownKeysOfTupleType(t) : getIndexType(t); } if (type.flags & TypeFlags.Conditional) { if ((type as ConditionalType).root.isDistributive) { const checkType = (type as ConditionalType).checkType; const constraint = getLowerBoundOfKeyType(checkType); if (constraint !== checkType) { return getConditionalTypeInstantiation(type as ConditionalType, prependTypeMapping((type as ConditionalType).root.checkType, constraint, (type as ConditionalType).mapper), /*forConstraint*/ false); } } return type; } if (type.flags & TypeFlags.Union) { return mapType(type as UnionType, getLowerBoundOfKeyType, /*noReductions*/ true); } if (type.flags & TypeFlags.Intersection) { // Similarly to getTypeFromIntersectionTypeNode, we preserve the special string & {}, number & {}, // and bigint & {} intersections that are used to prevent subtype reduction in union types. const types = (type as IntersectionType).types; if (types.length === 2 && !!(types[0].flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && types[1] === emptyTypeLiteralType) { return type; } return getIntersectionType(sameMap((type as UnionType).types, getLowerBoundOfKeyType)); } return type; } function getIsLateCheckFlag(s: Symbol): CheckFlags { return getCheckFlags(s) & CheckFlags.Late; } function forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(type: Type, include: TypeFlags, stringsOnly: boolean, cb: (keyType: Type) => void) { for (const prop of getPropertiesOfType(type)) { cb(getLiteralTypeFromProperty(prop, include)); } if (type.flags & TypeFlags.Any) { cb(stringType); } else { for (const info of getIndexInfosOfType(type)) { if (!stringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { cb(info.keyType); } } } } /** Resolve the members of a mapped type { [P in K]: T } */ function resolveMappedTypeMembers(type: MappedType) { const members: SymbolTable = createSymbolTable(); let indexInfos: IndexInfo[] | undefined; // Resolve upfront such that recursive references see an empty object type. setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, // and T as the template type. const typeParameter = getTypeParameterFromMappedType(type); const constraintType = getConstraintTypeFromMappedType(type); const mappedType = (type.target as MappedType) || type; const nameType = getNameTypeFromMappedType(mappedType); const shouldLinkPropDeclarations = getMappedTypeNameTypeKind(mappedType) !== MappedTypeNameTypeKind.Remapping; const templateType = getTemplateTypeFromMappedType(mappedType); const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' const templateModifiers = getMappedTypeModifiers(type); const include = TypeFlags.StringOrNumberLiteralOrUnique; if (isMappedTypeWithKeyofConstraintDeclaration(type)) { // We have a { [P in keyof T]: X } forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, /*stringsOnly*/ false, addMemberForKeyType); } else { forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); } setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); function addMemberForKeyType(keyType: Type) { const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; forEachType(propNameType, t => addMemberForKeyTypeWorker(keyType, t)); } function addMemberForKeyTypeWorker(keyType: Type, propNameType: Type) { // If the current iteration type constituent is a string literal type, create a property. // Otherwise, for type string create a string index signature. if (isTypeUsableAsPropertyName(propNameType)) { const propName = getPropertyNameFromType(propNameType); // String enum members from separate enums with identical values // are distinct types with the same property name. Make the resulting // property symbol's name type be the union of those enum member types. const existingProp = members.get(propName) as MappedSymbol | undefined; if (existingProp) { existingProp.links.nameType = getUnionType([existingProp.links.nameType!, propNameType]); existingProp.links.keyType = getUnionType([existingProp.links.keyType, keyType]); } else { const modifiersProp = isTypeUsableAsPropertyName(keyType) ? getPropertyOfType(modifiersType, getPropertyNameFromType(keyType)) : undefined; const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional || !(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional); const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp)); const stripOptional = strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional; const lateFlag: CheckFlags = modifiersProp ? getIsLateCheckFlag(modifiersProp) : 0; const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, lateFlag | CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0)) as MappedSymbol; prop.links.mappedType = type; prop.links.nameType = propNameType; prop.links.keyType = keyType; if (modifiersProp) { prop.links.syntheticOrigin = modifiersProp; prop.declarations = shouldLinkPropDeclarations ? modifiersProp.declarations : undefined; } members.set(propName, prop); } } else if (isValidIndexKeyType(propNameType) || propNameType.flags & (TypeFlags.Any | TypeFlags.Enum)) { const indexKeyType = propNameType.flags & (TypeFlags.Any | TypeFlags.String) ? stringType : propNameType.flags & (TypeFlags.Number | TypeFlags.Enum) ? numberType : propNameType; const propType = instantiateType(templateType, appendTypeMapping(type.mapper, typeParameter, keyType)); const modifiersIndexInfo = getApplicableIndexInfo(modifiersType, propNameType); const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersIndexInfo?.isReadonly); const indexInfo = createIndexInfo(indexKeyType, propType, isReadonly); indexInfos = appendIndexInfo(indexInfos, indexInfo, /*union*/ true); } } } function getTypeOfMappedSymbol(symbol: MappedSymbol) { if (!symbol.links.type) { const mappedType = symbol.links.mappedType; if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { mappedType.containsError = true; return errorType; } const templateType = getTemplateTypeFromMappedType(mappedType.target as MappedType || mappedType); const mapper = appendTypeMapping(mappedType.mapper, getTypeParameterFromMappedType(mappedType), symbol.links.keyType); const propType = instantiateType(templateType, mapper); // When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the // type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks // mode, if the underlying property is optional we remove 'undefined' from the type. let type = strictNullChecks && symbol.flags & SymbolFlags.Optional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : symbol.links.checkFlags & CheckFlags.StripOptional ? removeMissingOrUndefinedType(propType) : propType; if (!popTypeResolution()) { error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(mappedType)); type = errorType; } symbol.links.type ??= type; } return symbol.links.type; } function getTypeParameterFromMappedType(type: MappedType) { return type.typeParameter || (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(type.declaration.typeParameter))); } function getConstraintTypeFromMappedType(type: MappedType) { return type.constraintType || (type.constraintType = getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)) || errorType); } function getNameTypeFromMappedType(type: MappedType) { return type.declaration.nameType ? type.nameType || (type.nameType = instantiateType(getTypeFromTypeNode(type.declaration.nameType), type.mapper)) : undefined; } function getTemplateTypeFromMappedType(type: MappedType) { return type.templateType || (type.templateType = type.declaration.type ? instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), /*isProperty*/ true, !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), type.mapper) : errorType); } function getConstraintDeclarationForMappedType(type: MappedType) { return getEffectiveConstraintOfTypeParameter(type.declaration.typeParameter); } function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) { const constraintDeclaration = getConstraintDeclarationForMappedType(type)!; // TODO: GH#18217 return constraintDeclaration.kind === SyntaxKind.TypeOperator && (constraintDeclaration as TypeOperatorNode).operator === SyntaxKind.KeyOfKeyword; } function getModifiersTypeFromMappedType(type: MappedType) { if (!type.modifiersType) { if (isMappedTypeWithKeyofConstraintDeclaration(type)) { // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves // 'keyof T' to a literal union type and we can't recover T from that type. type.modifiersType = instantiateType(getTypeFromTypeNode((getConstraintDeclarationForMappedType(type) as TypeOperatorNode).type), type.mapper); } else { // Otherwise, get the declared constraint type, and if the constraint type is a type parameter, // get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T', // the modifiers type is T. Otherwise, the modifiers type is unknown. const declaredType = getTypeFromMappedTypeNode(type.declaration) as MappedType; const constraint = getConstraintTypeFromMappedType(declaredType); const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(constraint as TypeParameter) : constraint; type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((extendedConstraint as IndexType).type, type.mapper) : unknownType; } } return type.modifiersType; } function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { const declaration = type.declaration; return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) | (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); } // Return -1, 0, or 1, where -1 means optionality is stripped (i.e. -?), 0 means optionality is unchanged, and 1 means // optionality is added (i.e. +?). function getMappedTypeOptionality(type: MappedType): number { const modifiers = getMappedTypeModifiers(type); return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; } // Return -1, 0, or 1, for stripped, unchanged, or added optionality respectively. When a homomorphic mapped type doesn't // modify optionality, recursively consult the optionality of the type being mapped over to see if it strips or adds optionality. // For intersections, return -1 or 1 when all constituents strip or add optionality, otherwise return 0. function getCombinedMappedTypeOptionality(type: Type): number { if (getObjectFlags(type) & ObjectFlags.Mapped) { return getMappedTypeOptionality(type as MappedType) || getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(type as MappedType)); } if (type.flags & TypeFlags.Intersection) { const optionality = getCombinedMappedTypeOptionality((type as IntersectionType).types[0]); return every((type as IntersectionType).types, (t, i) => i === 0 || getCombinedMappedTypeOptionality(t) === optionality) ? optionality : 0; } return 0; } function isPartialMappedType(type: Type) { return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers(type as MappedType) & MappedTypeModifiers.IncludeOptional); } function isGenericMappedType(type: Type): type is MappedType { if (getObjectFlags(type) & ObjectFlags.Mapped) { const constraint = getConstraintTypeFromMappedType(type as MappedType); if (isGenericIndexType(constraint)) { return true; } // A mapped type is generic if the 'as' clause references generic types other than the iteration type. // To determine this, we substitute the constraint type (that we now know isn't generic) for the iteration // type and check whether the resulting type is generic. const nameType = getNameTypeFromMappedType(type as MappedType); if (nameType && isGenericIndexType(instantiateType(nameType, makeUnaryTypeMapper(getTypeParameterFromMappedType(type as MappedType), constraint)))) { return true; } } return false; } function getMappedTypeNameTypeKind(type: MappedType): MappedTypeNameTypeKind { const nameType = getNameTypeFromMappedType(type); if (!nameType) { return MappedTypeNameTypeKind.None; } return isTypeAssignableTo(nameType, getTypeParameterFromMappedType(type)) ? MappedTypeNameTypeKind.Filtering : MappedTypeNameTypeKind.Remapping; } function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { if (!(type as ResolvedType).members) { if (type.flags & TypeFlags.Object) { if ((type as ObjectType).objectFlags & ObjectFlags.Reference) { resolveTypeReferenceMembers(type as TypeReference); } else if ((type as ObjectType).objectFlags & ObjectFlags.ClassOrInterface) { resolveClassOrInterfaceMembers(type as InterfaceType); } else if ((type as ReverseMappedType).objectFlags & ObjectFlags.ReverseMapped) { resolveReverseMappedTypeMembers(type as ReverseMappedType); } else if ((type as ObjectType).objectFlags & ObjectFlags.Anonymous) { resolveAnonymousTypeMembers(type as AnonymousType); } else if ((type as MappedType).objectFlags & ObjectFlags.Mapped) { resolveMappedTypeMembers(type as MappedType); } else { Debug.fail("Unhandled object type " + Debug.formatObjectFlags(type.objectFlags)); } } else if (type.flags & TypeFlags.Union) { resolveUnionTypeMembers(type as UnionType); } else if (type.flags & TypeFlags.Intersection) { resolveIntersectionTypeMembers(type as IntersectionType); } else { Debug.fail("Unhandled type " + Debug.formatTypeFlags(type.flags)); } } return type as ResolvedType; } /** Return properties of an object type or an empty array for other types */ function getPropertiesOfObjectType(type: Type): Symbol[] { if (type.flags & TypeFlags.Object) { return resolveStructuredTypeMembers(type as ObjectType).properties; } return emptyArray; } /** If the given type is an object type and that type has a property by the given name, * return the symbol for that property. Otherwise return undefined. */ function getPropertyOfObjectType(type: Type, name: __String): Symbol | undefined { if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type as ObjectType); const symbol = resolved.members.get(name); if (symbol && symbolIsValue(symbol)) { return symbol; } } } function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): Symbol[] { if (!type.resolvedProperties) { const members = createSymbolTable(); for (const current of type.types) { for (const prop of getPropertiesOfType(current)) { if (!members.has(prop.escapedName)) { const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName, /*skipObjectFunctionPropertyAugment*/ !!(type.flags & TypeFlags.Intersection)); if (combinedProp) { members.set(prop.escapedName, combinedProp); } } } // The properties of a union type are those that are present in all constituent types, so // we only need to check the properties of the first type without index signature if (type.flags & TypeFlags.Union && getIndexInfosOfType(current).length === 0) { break; } } type.resolvedProperties = getNamedMembers(members); } return type.resolvedProperties; } function getPropertiesOfType(type: Type): Symbol[] { type = getReducedApparentType(type); return type.flags & TypeFlags.UnionOrIntersection ? getPropertiesOfUnionOrIntersectionType(type as UnionType) : getPropertiesOfObjectType(type); } function forEachPropertyOfType(type: Type, action: (symbol: Symbol, escapedName: __String) => void): void { type = getReducedApparentType(type); if (type.flags & TypeFlags.StructuredType) { resolveStructuredTypeMembers(type as StructuredType).members.forEach((symbol, escapedName) => { if (isNamedMember(symbol, escapedName)) { action(symbol, escapedName); } }); } } function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { const list = obj.properties as NodeArray; return list.some(property => { const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(property.name)) : getLiteralTypeFromPropertyName(property.name)); const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name); return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected); }); } function getAllPossiblePropertiesOfTypes(types: readonly Type[]): Symbol[] { const unionType = getUnionType(types); if (!(unionType.flags & TypeFlags.Union)) { return getAugmentedPropertiesOfType(unionType); } const props = createSymbolTable(); for (const memberType of types) { for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) { if (!props.has(escapedName)) { const prop = createUnionOrIntersectionProperty(unionType as UnionType, escapedName); // May be undefined if the property is private if (prop) props.set(escapedName, prop); } } } return arrayFrom(props.values()); } function getConstraintOfType(type: InstantiableType | UnionOrIntersectionType): Type | undefined { return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(type as TypeParameter) : type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(type as IndexedAccessType) : type.flags & TypeFlags.Conditional ? getConstraintOfConditionalType(type as ConditionalType) : getBaseConstraintOfType(type); } function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type | undefined { return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; } function isConstMappedType(type: MappedType, depth: number): boolean { const typeVariable = getHomomorphicTypeVariable(type); return !!typeVariable && isConstTypeVariable(typeVariable, depth); } function isConstTypeVariable(type: Type | undefined, depth = 0): boolean { return depth hasSyntacticModifier(d, ModifierFlags.Const)) || type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isConstTypeVariable(t, depth)) || type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType, depth + 1) || type.flags & TypeFlags.Conditional && isConstTypeVariable(getConstraintOfConditionalType(type as ConditionalType), depth + 1) || type.flags & TypeFlags.Substitution && isConstTypeVariable((type as SubstitutionType).baseType, depth) || getObjectFlags(type) & ObjectFlags.Mapped && isConstMappedType(type as MappedType, depth) || isGenericTupleType(type) && findIndex(getElementTypes(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t, depth)) >= 0 )); } function getConstraintOfIndexedAccess(type: IndexedAccessType) { return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined; } function getSimplifiedTypeOrConstraint(type: Type) { const simplified = getSimplifiedType(type, /*writing*/ false); return simplified !== type ? simplified : getConstraintOfType(type); } function getConstraintFromIndexedAccess(type: IndexedAccessType) { if (isMappedTypeGenericIndexedAccess(type)) { // For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic, // we substitute an instantiation of E where P is replaced with X. return substituteIndexedMappedType(type.objectType as MappedType, type.indexType); } const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); if (indexConstraint && indexConstraint !== type.indexType) { const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags); if (indexedAccess) { return indexedAccess; } } const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); if (objectConstraint && objectConstraint !== type.objectType) { return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType, type.accessFlags); } return undefined; } function getDefaultConstraintOfConditionalType(type: ConditionalType) { if (!type.resolvedDefaultConstraint) { // An `any` branch of a conditional type would normally be viral - specifically, without special handling here, // a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to // just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type, // in effect treating `any` like `never` rather than `unknown` in this location. const trueConstraint = getInferredTrueTypeFromConditionalType(type); const falseConstraint = getFalseTypeFromConditionalType(type); type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]); } return type.resolvedDefaultConstraint; } function getConstraintOfDistributiveConditionalType(type: ConditionalType): Type | undefined { if (type.resolvedConstraintOfDistributive !== undefined) { return type.resolvedConstraintOfDistributive || undefined; } // Check if we have a conditional type of the form 'T extends U ? X : Y', where T is a constrained // type parameter. If so, create an instantiation of the conditional type where T is replaced // with its constraint. We do this because if the constraint is a union type it will be distributed // over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T' // removes 'undefined' from T. // We skip returning a distributive constraint for a restrictive instantiation of a conditional type // as the constraint for all type params (check type included) have been replace with `unknown`, which // is going to produce even more false positive/negative results than the distribute constraint already does. // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter // a union - once negated types exist and are applied to the conditional false branch, this "constraint" // likely doesn't need to exist. if (type.root.isDistributive && type.restrictiveInstantiation !== type) { const simplified = getSimplifiedType(type.checkType, /*writing*/ false); const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified; if (constraint && constraint !== type.checkType) { const instantiated = getConditionalTypeInstantiation(type, prependTypeMapping(type.root.checkType, constraint, type.mapper), /*forConstraint*/ true); if (!(instantiated.flags & TypeFlags.Never)) { type.resolvedConstraintOfDistributive = instantiated; return instantiated; } } } type.resolvedConstraintOfDistributive = false; return undefined; } function getConstraintFromConditionalType(type: ConditionalType) { return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type); } function getConstraintOfConditionalType(type: ConditionalType) { return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined; } function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) { let constraints: Type[] | undefined; let hasDisjointDomainType = false; for (const t of types) { if (t.flags & TypeFlags.Instantiable) { // We keep following constraints as long as we have an instantiable type that is known // not to be circular or infinite (hence we stop on index access types). let constraint = getConstraintOfType(t); while (constraint && constraint.flags & (TypeFlags.TypeParameter | TypeFlags.Index | TypeFlags.Conditional)) { constraint = getConstraintOfType(constraint); } if (constraint) { constraints = append(constraints, constraint); if (targetIsUnion) { constraints = append(constraints, t); } } } else if (t.flags & TypeFlags.DisjointDomains || isEmptyAnonymousObjectType(t)) { hasDisjointDomainType = true; } } // If the target is a union type or if we are intersecting with types belonging to one of the // disjoint domains, we may end up producing a constraint that hasn't been examined before. if (constraints && (targetIsUnion || hasDisjointDomainType)) { if (hasDisjointDomainType) { // We add any types belong to one of the disjoint domains because they might cause the final // intersection operation to reduce the union constraints. for (const t of types) { if (t.flags & TypeFlags.DisjointDomains || isEmptyAnonymousObjectType(t)) { constraints = append(constraints, t); } } } // The source types were normalized; ensure the result is normalized too. return getNormalizedType(getIntersectionType(constraints, IntersectionFlags.NoConstraintReduction), /*writing*/ false); } return undefined; } function getBaseConstraintOfType(type: Type): Type | undefined { if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || isGenericTupleType(type)) { const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType); return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; } return type.flags & TypeFlags.Index ? stringNumberSymbolType : undefined; } /** * This is similar to `getBaseConstraintOfType` except it returns the input type if there's no base constraint, instead of `undefined` * It also doesn't map indexes to `string`, as where this is used this would be unneeded (and likely undesirable) */ function getBaseConstraintOrType(type: Type) { return getBaseConstraintOfType(type) || type; } function hasNonCircularBaseConstraint(type: InstantiableType): boolean { return getResolvedBaseConstraint(type) !== circularConstraintType; } /** * Return the resolved base constraint of a type variable. The noConstraintType singleton is returned if the * type variable has no constraint, and the circularConstraintType singleton is returned if the constraint * circularly references the type variable. */ function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type { if (type.resolvedBaseConstraint) { return type.resolvedBaseConstraint; } const stack: object[] = []; return type.resolvedBaseConstraint = getImmediateBaseConstraint(type); function getImmediateBaseConstraint(t: Type): Type { if (!t.immediateBaseConstraint) { if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) { return circularConstraintType; } let result; // We always explore at least 10 levels of nested constraints. Thereafter, we continue to explore // up to 50 levels of nested constraints provided there are no "deeply nested" types on the stack // (i.e. no types for which five instantiations have been recorded on the stack). If we reach 50 // levels of nesting, we are presumably exploring a repeating pattern with a long cycle that hasn't // yet triggered the deeply nested limiter. We have no test cases that actually get to 50 levels of // nesting, so it is effectively just a safety stop. const identity = getRecursionIdentity(t); if (stack.length { const constraint = v.flags & TypeFlags.TypeParameter && t.target.elementFlags[i] & ElementFlags.Variadic && getBaseConstraint(v) || v; return constraint !== v && everyType(constraint, c => isArrayOrTupleType(c) && !isGenericTupleType(c)) ? constraint : v; }); return createTupleType(newElements, t.target.elementFlags, t.target.readonly, t.target.labeledElementDeclarations); } return t; } } function getApparentTypeOfIntersectionType(type: IntersectionType, thisArgument: Type) { if (type === thisArgument) { return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, thisArgument, /*needApparentType*/ true)); } const key = `I${getTypeId(type)},${getTypeId(thisArgument)}`; return getCachedType(key) ?? setCachedType(key, getTypeWithThisArgument(type, thisArgument, /*needApparentType*/ true)); } function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined { if (!typeParameter.default) { if (typeParameter.target) { const targetDefault = getResolvedTypeParameterDefault(typeParameter.target); typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType; } else { // To block recursion, set the initial value to the resolvingDefaultType. typeParameter.default = resolvingDefaultType; const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default); const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType; if (typeParameter.default === resolvingDefaultType) { // If we have not been called recursively, set the correct default type. typeParameter.default = defaultType; } } } else if (typeParameter.default === resolvingDefaultType) { // If we are called recursively for this type parameter, mark the default as circular. typeParameter.default = circularConstraintType; } return typeParameter.default; } /** * Gets the default type for a type parameter. * * If the type parameter is the result of an instantiation, this gets the instantiated * default type of its target. If the type parameter has no default type or the default is * circular, `undefined` is returned. */ function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined { const defaultType = getResolvedTypeParameterDefault(typeParameter); return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined; } function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; } /** * Indicates whether the declaration of a typeParameter has a default type. */ function hasTypeParameterDefault(typeParameter: TypeParameter): boolean { return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default)); } function getApparentTypeOfMappedType(type: MappedType) { return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); } function getResolvedApparentTypeOfMappedType(type: MappedType): Type { const target = (type.target ?? type) as MappedType; const typeVariable = getHomomorphicTypeVariable(target); if (typeVariable && !target.declaration.nameType) { // We have a homomorphic mapped type or an instantiation of a homomorphic mapped type, i.e. a type // of the form { [P in keyof T]: X }. Obtain the modifiers type (the T of the keyof T), and if it is // another generic mapped type, recursively obtain its apparent type. Otherwise, obtain its base // constraint. Then, if every constituent of the base constraint is an array or tuple type, apply // this mapped type to the base constraint. It is safe to recurse when the modifiers type is a // mapped type because we protect again circular constraints in getTypeFromMappedTypeNode. const modifiersType = getModifiersTypeFromMappedType(type); const baseConstraint = isGenericMappedType(modifiersType) ? getApparentTypeOfMappedType(modifiersType) : getBaseConstraintOfType(modifiersType); if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) { return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper)); } } return type; } function isArrayOrTupleOrIntersection(type: Type) { return !!(type.flags & TypeFlags.Intersection) && every((type as IntersectionType).types, isArrayOrTupleType); } function isMappedTypeGenericIndexedAccess(type: Type) { let objectType; return !!(type.flags & TypeFlags.IndexedAccess && getObjectFlags(objectType = (type as IndexedAccessType).objectType) & ObjectFlags.Mapped && !isGenericMappedType(objectType) && isGenericIndexType((type as IndexedAccessType).indexType) && !(getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.ExcludeOptional) && !(objectType as MappedType).declaration.nameType); } /** * For a type parameter, return the base constraint of the type parameter. For the string, number, * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the * type itself. */ function getApparentType(type: Type): Type { const t = type.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(type) || unknownType : type; const objectFlags = getObjectFlags(t); return objectFlags & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : objectFlags & ObjectFlags.Reference && t !== type ? getTypeWithThisArgument(t, type) : t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType, type) : t.flags & TypeFlags.StringLike ? globalStringType : t.flags & TypeFlags.NumberLike ? globalNumberType : t.flags & TypeFlags.BigIntLike ? getGlobalBigIntType() : t.flags & TypeFlags.BooleanLike ? globalBooleanType : t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType() : t.flags & TypeFlags.NonPrimitive ? emptyObjectType : t.flags & TypeFlags.Index ? stringNumberSymbolType : t.flags & TypeFlags.Unknown && !strictNullChecks ? emptyObjectType : t; } function getReducedApparentType(type: Type): Type { // Since getApparentType may return a non-reduced union or intersection type, we need to perform // type reduction both before and after obtaining the apparent type. For example, given a type parameter // 'T extends A | B', the type 'T & X' becomes 'A & X | B & X' after obtaining the apparent type, and // that type may need further reduction to remove empty intersections. return getReducedType(getApparentType(getReducedType(type))); } function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { let propFlags = SymbolFlags.None; let singleProp: Symbol | undefined; let propSet: Map | undefined; let indexTypes: Type[] | undefined; const isUnion = containingType.flags & TypeFlags.Union; // Flags we want to propagate to the result if they exist in all source symbols let optionalFlag: SymbolFlags | undefined; let syntheticFlag = CheckFlags.SyntheticMethod; let checkFlags = isUnion ? 0 : CheckFlags.Readonly; let mergedInstantiations = false; for (const current of containingType.types) { const type = getApparentType(current); if (!(isErrorType(type) || type.flags & TypeFlags.Never)) { const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment); const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; if (prop) { if (prop.flags & SymbolFlags.ClassMember) { optionalFlag ??= isUnion ? SymbolFlags.None : SymbolFlags.Optional; if (isUnion) { optionalFlag |= prop.flags & SymbolFlags.Optional; } else { optionalFlag &= prop.flags; } } if (!singleProp) { singleProp = prop; propFlags = (prop.flags & SymbolFlags.Accessor) || SymbolFlags.Property; } else if (prop !== singleProp) { const isInstantiation = (getTargetSymbol(prop) || prop) === (getTargetSymbol(singleProp) || singleProp); // If the symbols are instances of one another with identical types - consider the symbols // equivalent and just use the first one, which thus allows us to avoid eliding private // members when intersecting a (this-)instantiations of a class with its raw base or another instance if (isInstantiation && compareProperties(singleProp, prop, (a, b) => a === b ? Ternary.True : Ternary.False) === Ternary.True) { // If we merged instantiations of a generic type, we replicate the symbol parent resetting behavior we used // to do when we recorded multiple distinct symbols so that we still get, eg, `Array.length` printed // back and not `Array.length` when we're looking at a `.length` access on a `string[] | number[]` mergedInstantiations = !!singleProp.parent && !!length(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(singleProp.parent)); } else { if (!propSet) { propSet = new Map(); propSet.set(getSymbolId(singleProp), singleProp); } const id = getSymbolId(prop); if (!propSet.has(id)) { propSet.set(id, prop); } } // classes created by mixins are represented as intersections // and overriding a property in a derived class redefines it completely at runtime // so a get accessor can't be merged with a set accessor in a base class, // for that reason the accessor flags are only used when they are the same in all constituents if (propFlags & SymbolFlags.Accessor && (prop.flags & SymbolFlags.Accessor) !== (propFlags & SymbolFlags.Accessor)) { propFlags = (propFlags & ~SymbolFlags.Accessor) | SymbolFlags.Property; } } if (isUnion && isReadonlySymbol(prop)) { checkFlags |= CheckFlags.Readonly; } else if (!isUnion && !isReadonlySymbol(prop)) { checkFlags &= ~CheckFlags.Readonly; } checkFlags |= (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); if (!isPrototypeProperty(prop)) { syntheticFlag = CheckFlags.SyntheticProperty; } } else if (isUnion) { const indexInfo = !isLateBoundName(name) && getApplicableIndexInfoForName(type, name); if (indexInfo) { propFlags = (propFlags & ~SymbolFlags.Accessor) | SymbolFlags.Property; checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0); indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); } else if (isObjectLiteralType(type) && !(getObjectFlags(type) & ObjectFlags.ContainsSpread)) { checkFlags |= CheckFlags.WritePartial; indexTypes = append(indexTypes, undefinedType); } else { checkFlags |= CheckFlags.ReadPartial; } } } } if ( !singleProp || isUnion && (propSet || checkFlags & CheckFlags.Partial) && checkFlags & (CheckFlags.ContainsPrivate | CheckFlags.ContainsProtected) && !(propSet && getCommonDeclarationsOfSymbols(propSet.values())) ) { // No property was found, or, in a union, a property has a private or protected declaration in one // constituent, but is missing or has a different declaration in another constituent. return undefined; } if (!propSet && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { if (mergedInstantiations) { // No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents) // Unless that parent is "reconstituted" from the "first value declaration" on the symbol (which is likely different than its instantiated parent!) // They also have a `.containingType` set, which affects some services endpoints behavior, like `getRootSymbol` const links = tryCast(singleProp, isTransientSymbol)?.links; const clone = createSymbolWithType(singleProp, links?.type); clone.parent = singleProp.valueDeclaration?.symbol?.parent; clone.links.containingType = containingType; clone.links.mapper = links?.mapper; clone.links.writeType = getWriteTypeOfSymbol(singleProp); return clone; } else { return singleProp; } } const props = propSet ? arrayFrom(propSet.values()) : [singleProp]; let declarations: Declaration[] | undefined; let firstType: Type | undefined; let nameType: Type | undefined; const propTypes: Type[] = []; let writeTypes: Type[] | undefined; let firstValueDeclaration: Declaration | undefined; let hasNonUniformValueDeclaration = false; for (const prop of props) { if (!firstValueDeclaration) { firstValueDeclaration = prop.valueDeclaration; } else if (prop.valueDeclaration && prop.valueDeclaration !== firstValueDeclaration) { hasNonUniformValueDeclaration = true; } declarations = addRange(declarations, prop.declarations); const type = getTypeOfSymbol(prop); if (!firstType) { firstType = type; nameType = getSymbolLinks(prop).nameType; } const writeType = getWriteTypeOfSymbol(prop); if (writeTypes || writeType !== type) { writeTypes = append(!writeTypes ? propTypes.slice() : writeTypes, writeType); } if (type !== firstType) { checkFlags |= CheckFlags.HasNonUniformType; } if (isLiteralType(type) || isPatternLiteralType(type)) { checkFlags |= CheckFlags.HasLiteralType; } if (type.flags & TypeFlags.Never && type !== uniqueLiteralType) { checkFlags |= CheckFlags.HasNeverType; } propTypes.push(type); } addRange(propTypes, indexTypes); const result = createSymbol(propFlags | (optionalFlag ?? 0), name, syntheticFlag | checkFlags); result.links.containingType = containingType; if (!hasNonUniformValueDeclaration && firstValueDeclaration) { result.valueDeclaration = firstValueDeclaration; // Inherit information about parent type. if (firstValueDeclaration.symbol.parent) { result.parent = firstValueDeclaration.symbol.parent; } } result.declarations = declarations; result.links.nameType = nameType; if (propTypes.length > 2) { // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed result.links.checkFlags |= CheckFlags.DeferredType; result.links.deferralParent = containingType; result.links.deferralConstituents = propTypes; result.links.deferralWriteConstituents = writeTypes; } else { result.links.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); if (writeTypes) { result.links.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes); } } return result; } // Return the symbol for a given property in a union or intersection type, or undefined if the property // does not exist in any constituent type. Note that the returned property may only be present in some // constituents, in which case the isPartial flag is set when the containing type is union type. We need // these partial properties when identifying discriminant properties, but otherwise they are filtered out // and do not appear to be present in the union type. function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { let property = skipObjectFunctionPropertyAugment ? type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) : type.propertyCache?.get(name); if (!property) { property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); if (property) { const properties = skipObjectFunctionPropertyAugment ? type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : type.propertyCache ||= createSymbolTable(); properties.set(name, property); // Propagate an entry from the non-augmented cache to the augmented cache unless the property is partial. if (skipObjectFunctionPropertyAugment && !(getCheckFlags(property) & CheckFlags.Partial) && !type.propertyCache?.get(name)) { const properties = type.propertyCache ||= createSymbolTable(); properties.set(name, property); } } } return property; } function getCommonDeclarationsOfSymbols(symbols: Iterable) { let commonDeclarations: Set | undefined; for (const symbol of symbols) { if (!symbol.declarations) { return undefined; } if (!commonDeclarations) { commonDeclarations = new Set(symbol.declarations); continue; } commonDeclarations.forEach(declaration => { if (!contains(symbol.declarations, declaration)) { commonDeclarations!.delete(declaration); } }); if (commonDeclarations.size === 0) { return undefined; } } return commonDeclarations; } function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { const property = getUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); // We need to filter out partial properties in union types return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined; } /** * Return the reduced form of the given type. For a union type, it is a union of the normalized constituent types. * For an intersection of types containing one or more mututally exclusive discriminant properties, it is 'never'. * For all other types, it is simply the type itself. Discriminant properties are considered mutually exclusive when * no constituent property has type 'never', but the intersection of the constituent property types is 'never'. */ function getReducedType(type: Type): Type { if (type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections) { return (type as UnionType).resolvedReducedType || ((type as UnionType).resolvedReducedType = getReducedUnionType(type as UnionType)); } else if (type.flags & TypeFlags.Intersection) { if (!((type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) { (type as IntersectionType).objectFlags |= ObjectFlags.IsNeverIntersectionComputed | (some(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0); } return (type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type; } return type; } function getReducedUnionType(unionType: UnionType) { const reducedTypes = sameMap(unionType.types, getReducedType); if (reducedTypes === unionType.types) { return unionType; } const reduced = getUnionType(reducedTypes); if (reduced.flags & TypeFlags.Union) { (reduced as UnionType).resolvedReducedType = reduced; } return reduced; } function isNeverReducedProperty(prop: Symbol) { return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop); } function isDiscriminantWithNeverType(prop: Symbol) { // Return true for a synthetic non-optional property with non-uniform types, where at least one is // a literal type and none is never, that reduces to never. return !(prop.flags & SymbolFlags.Optional) && (getCheckFlags(prop) & (CheckFlags.Discriminant | CheckFlags.HasNeverType)) === CheckFlags.Discriminant && !!(getTypeOfSymbol(prop).flags & TypeFlags.Never); } function isConflictingPrivateProperty(prop: Symbol) { // Return true for a synthetic property with multiple declarations, at least one of which is private. return !prop.valueDeclaration && !!(getCheckFlags(prop) & CheckFlags.ContainsPrivate); } /** * A union type which is reducible upon instantiation (meaning some members are removed under certain instantiations) * must be kept generic, as that instantiation information needs to flow through the type system. By replacing all * type parameters in the union with a special never type that is treated as a literal in `getReducedType`, we can cause * the `getReducedType` logic to reduce the resulting type if possible (since only intersections with conflicting * literal-typed properties are reducible). */ function isGenericReducibleType(type: Type): boolean { return !!(type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections && some((type as UnionType).types, isGenericReducibleType) || type.flags & TypeFlags.Intersection && isReducibleIntersection(type as IntersectionType)); } function isReducibleIntersection(type: IntersectionType) { const uniqueFilled = type.uniqueLiteralFilledInstantiation || (type.uniqueLiteralFilledInstantiation = instantiateType(type, uniqueLiteralMapper)); return getReducedType(uniqueFilled) !== uniqueFilled; } function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) { if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsNeverIntersection) { const neverProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isDiscriminantWithNeverType); if (neverProp) { return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(neverProp)); } const privateProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isConflictingPrivateProperty); if (privateProp) { return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_exists_in_multiple_constituents_and_is_private_in_some, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(privateProp)); } } return errorInfo; } /** * Return the symbol for the property with the given name in the given type. Creates synthetic union properties when * necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from * Object and Function as appropriate. * * @param type a type to look up property from * @param name a name of property to look up in a given type */ function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean, includeTypeOnlyMembers?: boolean): Symbol | undefined { type = getReducedApparentType(type); if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type as ObjectType); const symbol = resolved.members.get(name); if (symbol && !includeTypeOnlyMembers && type.symbol?.flags & SymbolFlags.ValueModule && getSymbolLinks(type.symbol).typeOnlyExportStarMap?.has(name)) { // If this is the type of a module, `resolved.members.get(name)` might have effectively skipped over // an `export type * from './foo'`, leaving `symbolIsValue` unable to see that the symbol is being // viewed through a type-only export. return undefined; } if (symbol && symbolIsValue(symbol, includeTypeOnlyMembers)) { return symbol; } if (skipObjectFunctionPropertyAugment) return undefined; 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.Intersection) { const prop = getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, /*skipObjectFunctionPropertyAugment*/ true); if (prop) { return prop; } if (!skipObjectFunctionPropertyAugment) { return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); } return undefined; } if (type.flags & TypeFlags.Union) { return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); } return undefined; } function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): readonly Signature[] { if (type.flags & TypeFlags.StructuredType) { const resolved = resolveStructuredTypeMembers(type as ObjectType); return kind === SignatureKind.Call ? resolved.callSignatures : resolved.constructSignatures; } return emptyArray; } /** * Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and * maps primitive types and type parameters are to their apparent types. */ function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { const result = getSignaturesOfStructuredType(getReducedApparentType(type), kind); if (kind === SignatureKind.Call && !length(result) && type.flags & TypeFlags.Union) { if ((type as UnionType).arrayFallbackSignatures) { return (type as UnionType).arrayFallbackSignatures!; } // If the union is all different instantiations of a member of the global array type... let memberName: __String; if (everyType(type, t => !!t.symbol?.parent && isArrayOrTupleSymbol(t.symbol.parent) && (!memberName ? (memberName = t.symbol.escapedName, true) : memberName === t.symbol.escapedName))) { // Transform the type from `(A[] | B[])["member"]` to `(A | B)[]["member"]` (since we pretend array is covariant anyway) const arrayArg = mapType(type, t => getMappedType((isReadonlyArraySymbol(t.symbol.parent) ? globalReadonlyArrayType : globalArrayType).typeParameters![0], (t as AnonymousType).mapper!)); const arrayType = createArrayType(arrayArg, someType(type, t => isReadonlyArraySymbol(t.symbol.parent))); return (type as UnionType).arrayFallbackSignatures = getSignaturesOfType(getTypeOfPropertyOfType(arrayType, memberName!)!, kind); } (type as UnionType).arrayFallbackSignatures = result; } return result; } function isArrayOrTupleSymbol(symbol: Symbol | undefined) { if (!symbol || !globalArrayType.symbol || !globalReadonlyArrayType.symbol) { return false; } return !!getSymbolIfSameReference(symbol, globalArrayType.symbol) || !!getSymbolIfSameReference(symbol, globalReadonlyArrayType.symbol); } function isReadonlyArraySymbol(symbol: Symbol | undefined) { if (!symbol || !globalReadonlyArrayType.symbol) { return false; } return !!getSymbolIfSameReference(symbol, globalReadonlyArrayType.symbol); } function findIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { return find(indexInfos, info => info.keyType === keyType); } function findApplicableIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { // Index signatures for type 'string' are considered only when no other index signatures apply. let stringIndexInfo: IndexInfo | undefined; let applicableInfo: IndexInfo | undefined; let applicableInfos: IndexInfo[] | undefined; for (const info of indexInfos) { if (info.keyType === stringType) { stringIndexInfo = info; } else if (isApplicableIndexType(keyType, info.keyType)) { if (!applicableInfo) { applicableInfo = info; } else { (applicableInfos || (applicableInfos = [applicableInfo])).push(info); } } } // When more than one index signature is applicable we create a synthetic IndexInfo. Instead of computing // the intersected key type, we just use unknownType for the key type as nothing actually depends on the // keyType property of the returned IndexInfo. return applicableInfos ? createIndexInfo(unknownType, getIntersectionType(map(applicableInfos, info => info.type)), reduceLeft(applicableInfos, (isReadonly, info) => isReadonly && info.isReadonly, /*initial*/ true)) : applicableInfo ? applicableInfo : stringIndexInfo && isApplicableIndexType(keyType, stringType) ? stringIndexInfo : undefined; } function isApplicableIndexType(source: Type, target: Type): boolean { // A 'string' index signature applies to types assignable to 'string' or 'number', and a 'number' index // signature applies to types assignable to 'number', `${number}` and numeric string literal types. return isTypeAssignableTo(source, target) || target === stringType && isTypeAssignableTo(source, numberType) || target === numberType && (source === numericStringType || !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value)); } function getIndexInfosOfStructuredType(type: Type): readonly IndexInfo[] { if (type.flags & TypeFlags.StructuredType) { const resolved = resolveStructuredTypeMembers(type as ObjectType); return resolved.indexInfos; } return emptyArray; } function getIndexInfosOfType(type: Type): readonly IndexInfo[] { return getIndexInfosOfStructuredType(getReducedApparentType(type)); } // Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and // maps primitive types and type parameters are to their apparent types. function getIndexInfoOfType(type: Type, keyType: Type): IndexInfo | undefined { return findIndexInfo(getIndexInfosOfType(type), keyType); } // Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and // maps primitive types and type parameters are to their apparent types. function getIndexTypeOfType(type: Type, keyType: Type): Type | undefined { return getIndexInfoOfType(type, keyType)?.type; } function getApplicableIndexInfos(type: Type, keyType: Type): IndexInfo[] { return getIndexInfosOfType(type).filter(info => isApplicableIndexType(keyType, info.keyType)); } function getApplicableIndexInfo(type: Type, keyType: Type): IndexInfo | undefined { return findApplicableIndexInfo(getIndexInfosOfType(type), keyType); } function getApplicableIndexInfoForName(type: Type, name: __String): IndexInfo | undefined { return getApplicableIndexInfo(type, isLateBoundName(name) ? esSymbolType : getStringLiteralType(unescapeLeadingUnderscores(name))); } // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual // type checking functions). function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): readonly TypeParameter[] | undefined { let result: TypeParameter[] | undefined; for (const node of getEffectiveTypeParameterDeclarations(declaration)) { result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); } return result?.length ? result : isFunctionDeclaration(declaration) ? getSignatureOfTypeTag(declaration)?.typeParameters : undefined; } function symbolsToArray(symbols: SymbolTable): Symbol[] { const result: Symbol[] = []; symbols.forEach((symbol, id) => { if (!isReservedMemberName(id)) { result.push(symbol); } }); return result; } function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { if (isExternalModuleNameRelative(moduleName)) { return undefined; } const symbol = getSymbol(globals, '"' + moduleName + '"' as __String, SymbolFlags.ValueModule); // merged symbol is module declaration symbol combined with all augmentations return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; } function hasEffectiveQuestionToken(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { return hasQuestionToken(node) || isOptionalJSDocPropertyLikeTag(node) || isParameter(node) && isJSDocOptionalParameter(node); } function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { if (hasEffectiveQuestionToken(node)) { return true; } if (!isParameter(node)) { return false; } if (node.initializer) { const signature = getSignatureFromDeclaration(node.parent); const parameterIndex = node.parent.parameters.indexOf(node); Debug.assert(parameterIndex >= 0); // Only consider syntactic or instantiated parameters as optional, not `void` parameters as this function is used // in grammar checks and checking for `void` too early results in parameter types widening too early // and causes some noImplicitAny errors to be lost. return parameterIndex >= getMinArgumentCount(signature, MinArgumentCountFlags.StrongArityForUntypedJS | MinArgumentCountFlags.VoidIsNonOptional); } const iife = getImmediatelyInvokedFunctionExpression(node.parent); if (iife) { return !node.type && !node.dotDotDotToken && node.parent.parameters.indexOf(node) >= getEffectiveCallArguments(iife).length; } return false; } function isOptionalPropertyDeclaration(node: Declaration) { return isPropertyDeclaration(node) && !hasAccessorModifier(node) && node.questionToken; } function createTypePredicate(kind: TypePredicateKind, parameterName: string | undefined, parameterIndex: number | undefined, type: Type | undefined): TypePredicate { return { kind, parameterName, parameterIndex, type } as TypePredicate; } /** * Gets the minimum number of type arguments needed to satisfy all non-optional type * parameters. */ function getMinTypeArgumentCount(typeParameters: readonly TypeParameter[] | undefined): number { let minTypeArgumentCount = 0; if (typeParameters) { for (let i = 0; i = minTypeArgumentCount && numTypeArguments !!getJSDocType(p)) && !getJSDocType(declaration) && !getContextualSignatureForFunctionLikeDeclaration(declaration); if (isUntypedSignatureInJSFile) { flags |= SignatureFlags.IsUntypedSignatureInJSFile; } // If this is a JSDoc construct signature, then skip the first parameter in the // parameter list. The first parameter represents the return type of the construct // signature. for (let i = isJSConstructSignature ? 1 : 0; i iife.arguments.length && !type; if (!isOptionalParameter) { minArgumentCount = parameters.length; } } // If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation if ( (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) && hasBindableName(declaration) && (!hasThisParameter || !thisParameter) ) { const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; const other = getDeclarationOfKind(getSymbolOfDeclaration(declaration), otherKind); if (other) { thisParameter = getAnnotatedAccessorThisParameter(other); } } if (thisTag && thisTag.typeExpression) { thisParameter = createSymbolWithType(createSymbol(SymbolFlags.FunctionScopedVariable, InternalSymbolName.This), getTypeFromTypeNode(thisTag.typeExpression)); } const hostDeclaration = isJSDocSignature(declaration) ? getEffectiveJSDocHost(declaration) : declaration; const classType = hostDeclaration && isConstructorDeclaration(hostDeclaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol((hostDeclaration.parent as ClassDeclaration).symbol)) : undefined; const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { flags |= SignatureFlags.HasRestParameter; } if ( isConstructorTypeNode(declaration) && hasSyntacticModifier(declaration, ModifierFlags.Abstract) || isConstructorDeclaration(declaration) && hasSyntacticModifier(declaration.parent, ModifierFlags.Abstract) ) { flags |= SignatureFlags.Abstract; } links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, minArgumentCount, flags); } return links.resolvedSignature; } /** * A JS function gets a synthetic rest parameter if it references `arguments` AND: * 1. It has no parameters but at least one `@param` with a type that starts with `...` * OR * 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...` */ function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean { if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) { return false; } const lastParam = lastOrUndefined(declaration.parameters); const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag); const lastParamVariadicType = firstDefined(lastParamTags, p => p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined); const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String, CheckFlags.RestParameter); if (lastParamVariadicType) { // Parameter has effective annotation, lock in type syntheticArgsSymbol.links.type = createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)); } else { // Parameter has no annotation // By using a `DeferredType` symbol, we allow the type of this rest arg to be overriden by contextual type assignment so long as its type hasn't been // cached by `getTypeOfSymbol` yet. syntheticArgsSymbol.links.checkFlags |= CheckFlags.DeferredType; syntheticArgsSymbol.links.deferralParent = neverType; syntheticArgsSymbol.links.deferralConstituents = [anyArrayType]; syntheticArgsSymbol.links.deferralWriteConstituents = [anyArrayType]; } if (lastParamVariadicType) { // Replace the last parameter with a rest parameter. parameters.pop(); } parameters.push(syntheticArgsSymbol); return true; } function getSignatureOfTypeTag(node: SignatureDeclaration | JSDocSignature) { // should be attached to a function declaration or expression if (!(isInJSFile(node) && isFunctionLikeDeclaration(node))) return undefined; const typeTag = getJSDocTypeTag(node); return typeTag?.typeExpression && getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); } function getParameterTypeOfTypeTag(func: FunctionLikeDeclaration, parameter: ParameterDeclaration) { const signature = getSignatureOfTypeTag(func); if (!signature) return undefined; const pos = func.parameters.indexOf(parameter); return parameter.dotDotDotToken ? getRestTypeAtPosition(signature, pos) : getTypeAtPosition(signature, pos); } function getReturnTypeOfTypeTag(node: SignatureDeclaration | JSDocSignature) { const signature = getSignatureOfTypeTag(node); return signature && getReturnTypeOfSignature(signature); } function containsArgumentsReference(declaration: SignatureDeclaration): boolean { const links = getNodeLinks(declaration); if (links.containsArgumentsReference === undefined) { if (links.flags & NodeCheckFlags.CaptureArguments) { links.containsArgumentsReference = true; } else { links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body!); } } return links.containsArgumentsReference; function traverse(node: Node): boolean { if (!node) return false; switch (node.kind) { case SyntaxKind.Identifier: return (node as Identifier).escapedText === argumentsSymbol.escapedName && getReferencedValueSymbol(node as Identifier) === argumentsSymbol; case SyntaxKind.PropertyDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return (node as NamedDeclaration).name!.kind === SyntaxKind.ComputedPropertyName && traverse((node as NamedDeclaration).name!); case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: return traverse((node as PropertyAccessExpression | ElementAccessExpression).expression); case SyntaxKind.PropertyAssignment: return traverse((node as PropertyAssignment).initializer); default: return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && !!forEachChild(node, traverse); } } } function getSignaturesOfSymbol(symbol: Symbol | undefined): Signature[] { if (!symbol || !symbol.declarations) return emptyArray; const result: Signature[] = []; for (let i = 0; i 0 && (decl as FunctionLikeDeclaration).body) { const previous = symbol.declarations[i - 1]; if (decl.parent === previous.parent && decl.kind === previous.kind && decl.pos === previous.end) { continue; } } if (isInJSFile(decl) && decl.jsDoc) { const tags = getJSDocOverloadTags(decl); if (length(tags)) { for (const tag of tags) { const jsDocSignature = tag.typeExpression; if (jsDocSignature.type === undefined && !isConstructorDeclaration(decl)) { reportImplicitAny(jsDocSignature, anyType); } result.push(getSignatureFromDeclaration(jsDocSignature)); } continue; } } // If this is a function or method declaration, get the signature from the @type tag for the sake of optional parameters. // Exclude contextually-typed kinds because we already apply the @type tag to the context, plus applying it here to the initializer would supress checks that the two are compatible. result.push( (!isFunctionExpressionOrArrowFunction(decl) && !isObjectLiteralMethod(decl) && getSignatureOfTypeTag(decl)) || getSignatureFromDeclaration(decl), ); } return result; } function resolveExternalModuleTypeByLiteral(name: StringLiteral) { const moduleSym = resolveExternalModuleName(name, name); if (moduleSym) { const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); if (resolvedModuleSymbol) { return getTypeOfSymbol(resolvedModuleSymbol); } } return anyType; } function getThisTypeOfSignature(signature: Signature): Type | undefined { if (signature.thisParameter) { return getTypeOfSymbol(signature.thisParameter); } } function getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined { if (!signature.resolvedTypePredicate) { if (signature.target) { const targetTypePredicate = getTypePredicateOfSignature(signature.target); signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper!) : noTypePredicate; } else if (signature.compositeSignatures) { signature.resolvedTypePredicate = getUnionOrIntersectionTypePredicate(signature.compositeSignatures, signature.compositeKind) || noTypePredicate; } else { const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); let jsdocPredicate: TypePredicate | undefined; if (!type) { const jsdocSignature = getSignatureOfTypeTag(signature.declaration!); if (jsdocSignature && signature !== jsdocSignature) { jsdocPredicate = getTypePredicateOfSignature(jsdocSignature); } } if (type || jsdocPredicate) { signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? createTypePredicateFromTypePredicateNode(type, signature) : jsdocPredicate || noTypePredicate; } else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.Boolean) && getParameterCount(signature) > 0) { const { declaration } = signature; signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate; } else { signature.resolvedTypePredicate = noTypePredicate; } } Debug.assert(!!signature.resolvedTypePredicate); } return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate; } function createTypePredicateFromTypePredicateNode(node: TypePredicateNode, signature: Signature): TypePredicate { const parameterName = node.parameterName; const type = node.type && getTypeFromTypeNode(node.type); return parameterName.kind === SyntaxKind.ThisType ? createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsThis : TypePredicateKind.This, /*parameterName*/ undefined, /*parameterIndex*/ undefined, type) : createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsIdentifier : TypePredicateKind.Identifier, parameterName.escapedText as string, findIndex(signature.parameters, p => p.escapedName === parameterName.escapedText), type); } function getUnionOrIntersectionType(types: Type[], kind: TypeFlags | undefined, unionReduction?: UnionReduction) { return kind !== TypeFlags.Intersection ? getUnionType(types, unionReduction) : getIntersectionType(types); } function getReturnTypeOfSignature(signature: Signature): Type { if (!signature.resolvedReturnType) { if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) { return errorType; } let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper) : signature.compositeSignatures ? instantiateType(getUnionOrIntersectionType(map(signature.compositeSignatures, getReturnTypeOfSignature), signature.compositeKind, UnionReduction.Subtype), signature.mapper) : getReturnTypeFromAnnotation(signature.declaration!) || (nodeIsMissing((signature.declaration as FunctionLikeDeclaration).body) ? anyType : getReturnTypeFromBody(signature.declaration as FunctionLikeDeclaration)); if (signature.flags & SignatureFlags.IsInnerCallChain) { type = addOptionalTypeMarker(type); } else if (signature.flags & SignatureFlags.IsOuterCallChain) { type = getOptionalType(type); } if (!popTypeResolution()) { if (signature.declaration) { const typeNode = getEffectiveReturnTypeNode(signature.declaration); if (typeNode) { error(typeNode, Diagnostics.Return_type_annotation_circularly_references_itself); } else if (noImplicitAny) { const declaration = signature.declaration as Declaration; const name = getNameOfDeclaration(declaration); if (name) { error(name, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, declarationNameToString(name)); } else { error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions); } } } type = anyType; } signature.resolvedReturnType ??= type; } return signature.resolvedReturnType; } function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { if (declaration.kind === SyntaxKind.Constructor) { return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)); } const typeNode = getEffectiveReturnTypeNode(declaration); if (isJSDocSignature(declaration)) { const root = getJSDocRoot(declaration); if (root && isConstructorDeclaration(root.parent) && !typeNode) { return getDeclaredTypeOfClassOrInterface(getMergedSymbol((root.parent.parent as ClassDeclaration).symbol)); } } if (isJSDocConstructSignature(declaration)) { return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 } if (typeNode) { return getTypeFromTypeNode(typeNode); } if (declaration.kind === SyntaxKind.GetAccessor && hasBindableName(declaration)) { const jsDocType = isInJSFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration); if (jsDocType) { return jsDocType; } const setter = getDeclarationOfKind(getSymbolOfDeclaration(declaration), SyntaxKind.SetAccessor); const setterType = getAnnotatedAccessorType(setter); if (setterType) { return setterType; } } return getReturnTypeOfTypeTag(declaration); } function isResolvingReturnTypeOfSignature(signature: Signature): boolean { return signature.compositeSignatures && some(signature.compositeSignatures, isResolvingReturnTypeOfSignature) || !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0; } function getRestTypeOfSignature(signature: Signature): Type { return tryGetRestTypeOfSignature(signature) || anyType; } function tryGetRestTypeOfSignature(signature: Signature): Type | undefined { if (signatureHasRestParameter(signature)) { const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType; return restType && getIndexTypeOfType(restType, numberType); } return undefined; } function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature { const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript)); if (inferredTypeParameters) { const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature)); if (returnSignature) { const newReturnSignature = cloneSignature(returnSignature); newReturnSignature.typeParameters = inferredTypeParameters; const newReturnType = getOrCreateTypeFromSignature(newReturnSignature) as AnonymousType; newReturnType.mapper = instantiatedSignature.mapper; const newInstantiatedSignature = cloneSignature(instantiatedSignature); newInstantiatedSignature.resolvedReturnType = newReturnType; return newInstantiatedSignature; } } return instantiatedSignature; } function getSignatureInstantiationWithoutFillingInTypeArguments(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { const instantiations = signature.instantiations || (signature.instantiations = new Map()); const id = getTypeListId(typeArguments); let instantiation = instantiations.get(id); if (!instantiation) { instantiations.set(id, instantiation = createSignatureInstantiation(signature, typeArguments)); } return instantiation; } function createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); } function getTypeParametersForMapper(signature: Signature) { return sameMap(signature.typeParameters, tp => tp.mapper ? instantiateType(tp, tp.mapper) : tp); } function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { return createTypeMapper(getTypeParametersForMapper(signature)!, typeArguments); } function getErasedSignature(signature: Signature): Signature { return signature.typeParameters ? signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(signature)) : signature; } function createErasedSignature(signature: Signature) { // Create an instantiation of the signature where all type arguments are the any type. return instantiateSignature(signature, createTypeEraser(signature.typeParameters!), /*eraseTypeParameters*/ true); } function getCanonicalSignature(signature: Signature): Signature { return signature.typeParameters ? signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : signature; } function createCanonicalSignature(signature: Signature) { // Create an instantiation of the signature where each unconstrained type parameter is replaced with // its original. When a generic class or interface is instantiated, each generic method in the class or // interface is instantiated with a fresh set of cloned type parameters (which we need to handle scenarios // where different generations of the same type parameter are in scope). This leads to a lot of new type // identities, and potentially a lot of work comparing those identities, so here we create an instantiation // that uses the original type identities for all unconstrained type parameters. return getSignatureInstantiation( signature, map(signature.typeParameters, tp => tp.target && !getConstraintOfTypeParameter(tp.target) ? tp.target : tp), isInJSFile(signature.declaration), ); } function getBaseSignature(signature: Signature) { const typeParameters = signature.typeParameters; if (typeParameters) { if (signature.baseSignatureCache) { return signature.baseSignatureCache; } const typeEraser = createTypeEraser(typeParameters); const baseConstraintMapper = createTypeMapper(typeParameters, map(typeParameters, tp => getConstraintOfTypeParameter(tp) || unknownType)); let baseConstraints: readonly Type[] = map(typeParameters, tp => instantiateType(tp, baseConstraintMapper) || unknownType); // Run N type params thru the immediate constraint mapper up to N times // This way any noncircular interdependent type parameters are definitely resolved to their external dependencies for (let i = 0; i { if (isValidIndexKeyType(keyType) && !findIndexInfo(indexInfos, keyType)) { indexInfos.push(createIndexInfo(keyType, declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, hasEffectiveModifier(declaration, ModifierFlags.Readonly), declaration)); } }); } } } else if (hasLateBindableIndexSignature(declaration)) { const declName = isBinaryExpression(declaration) ? declaration.left as ElementAccessExpression : (declaration as LateBoundDeclaration).name; const keyType = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName); if (findIndexInfo(indexInfos, keyType)) { continue; // Explicit index for key type takes priority } if (isTypeAssignableTo(keyType, stringNumberSymbolType)) { if (isTypeAssignableTo(keyType, numberType)) { hasComputedNumberProperty = true; if (!hasEffectiveReadonlyModifier(declaration)) { readonlyComputedNumberProperty = false; } } else if (isTypeAssignableTo(keyType, esSymbolType)) { hasComputedSymbolProperty = true; if (!hasEffectiveReadonlyModifier(declaration)) { readonlyComputedSymbolProperty = false; } } else { hasComputedStringProperty = true; if (!hasEffectiveReadonlyModifier(declaration)) { readonlyComputedStringProperty = false; } } computedPropertySymbols.push(declaration.symbol); } } } const allPropertySymbols = concatenate(computedPropertySymbols, filter(siblingSymbols, s => s !== indexSymbol)); // aggregate similar index infos implied to be the same key to the same combined index info if (hasComputedStringProperty && !findIndexInfo(indexInfos, stringType)) indexInfos.push(getObjectLiteralIndexInfo(readonlyComputedStringProperty, 0, allPropertySymbols, stringType)); if (hasComputedNumberProperty && !findIndexInfo(indexInfos, numberType)) indexInfos.push(getObjectLiteralIndexInfo(readonlyComputedNumberProperty, 0, allPropertySymbols, numberType)); if (hasComputedSymbolProperty && !findIndexInfo(indexInfos, esSymbolType)) indexInfos.push(getObjectLiteralIndexInfo(readonlyComputedSymbolProperty, 0, allPropertySymbols, esSymbolType)); return indexInfos; } return emptyArray; } function isValidIndexKeyType(type: Type): boolean { return !!(type.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.ESSymbol)) || isPatternLiteralType(type) || !!(type.flags & TypeFlags.Intersection) && !isGenericType(type) && some((type as IntersectionType).types, isValidIndexKeyType); } function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined { return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0]; } function getInferredTypeParameterConstraint(typeParameter: TypeParameter, omitTypeReferences?: boolean) { let inferences: Type[] | undefined; if (typeParameter.symbol?.declarations) { for (const declaration of typeParameter.symbol.declarations) { if (declaration.parent.kind === SyntaxKind.InferType) { // When an 'infer T' declaration is immediately contained in a type reference node // (such as 'Foo'), T's constraint is inferred from the constraint of the // corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are // present, we form an intersection of the inferred constraint types. const [childTypeParameter = declaration.parent, grandParent] = walkUpParenthesizedTypesAndGetParentAndChild(declaration.parent.parent); if (grandParent.kind === SyntaxKind.TypeReference && !omitTypeReferences) { const typeReference = grandParent as TypeReferenceNode; const typeParameters = getTypeParametersForTypeReferenceOrImport(typeReference); if (typeParameters) { const index = typeReference.typeArguments!.indexOf(childTypeParameter as TypeNode); if (index = [T, U]; // type Bar = T extends Foo ? Foo : T; // the instantiated constraint for U is X, so we discard that inference. const mapper = makeDeferredTypeMapper( typeParameters, typeParameters.map((_, index) => () => { return getEffectiveTypeArgumentAtIndex(typeReference, typeParameters, index); }), ); const constraint = instantiateType(declaredConstraint, mapper); if (constraint !== typeParameter) { inferences = append(inferences, constraint); } } } } } // When an 'infer T' declaration is immediately contained in a rest parameter declaration, a rest type // or a named rest tuple element, we infer an 'unknown[]' constraint. else if ( grandParent.kind === SyntaxKind.Parameter && (grandParent as ParameterDeclaration).dotDotDotToken || grandParent.kind === SyntaxKind.RestType || grandParent.kind === SyntaxKind.NamedTupleMember && (grandParent as NamedTupleMember).dotDotDotToken ) { inferences = append(inferences, createArrayType(unknownType)); } // When an 'infer T' declaration is immediately contained in a string template type, we infer a 'string' // constraint. else if (grandParent.kind === SyntaxKind.TemplateLiteralTypeSpan) { inferences = append(inferences, stringType); } // When an 'infer T' declaration is in the constraint position of a mapped type, we infer a 'keyof any' // constraint. else if (grandParent.kind === SyntaxKind.TypeParameter && grandParent.parent.kind === SyntaxKind.MappedType) { inferences = append(inferences, stringNumberSymbolType); } // When an 'infer T' declaration is the template of a mapped type, and that mapped type is the extends // clause of a conditional whose check type is also a mapped type, give it a constraint equal to the template // of the check type's mapped type else if ( grandParent.kind === SyntaxKind.MappedType && (grandParent as MappedTypeNode).type && skipParentheses((grandParent as MappedTypeNode).type!) === declaration.parent && grandParent.parent.kind === SyntaxKind.ConditionalType && (grandParent.parent as ConditionalTypeNode).extendsType === grandParent && (grandParent.parent as ConditionalTypeNode).checkType.kind === SyntaxKind.MappedType && ((grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode).type ) { const checkMappedType = (grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode; const nodeType = getTypeFromTypeNode(checkMappedType.type!); inferences = append(inferences, instantiateType(nodeType, makeUnaryTypeMapper(getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : stringNumberSymbolType))); } } } } return inferences && getIntersectionType(inferences); } /** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */ function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type | undefined { if (!typeParameter.constraint) { if (typeParameter.target) { const targetConstraint = getConstraintOfTypeParameter(typeParameter.target); typeParameter.constraint = targetConstraint ? instantiateType(targetConstraint, typeParameter.mapper) : noConstraintType; } else { const constraintDeclaration = getConstraintDeclaration(typeParameter); if (!constraintDeclaration) { typeParameter.constraint = getInferredTypeParameterConstraint(typeParameter) || noConstraintType; } else { let type = getTypeFromTypeNode(constraintDeclaration); if (type.flags & TypeFlags.Any && !isErrorType(type)) { // Allow errorType to propegate to keep downstream errors suppressed // use stringNumberSymbolType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was), // use unknown otherwise type = constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType ? stringNumberSymbolType : unknownType; } typeParameter.constraint = type; } } } return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint; } function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; const host = isJSDocTemplateTag(tp.parent) ? getEffectiveContainerForJSDocTemplateTag(tp.parent) : tp.parent; return host && getSymbolOfNode(host); } function getTypeListId(types: readonly Type[] | undefined) { let result = ""; if (types) { const length = types.length; let i = 0; while (i 1) { result += ":" + count; } i += count; } } return result; } function getAliasId(aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { return aliasSymbol ? `@${getSymbolId(aliasSymbol)}` + (aliasTypeArguments ? `:${getTypeListId(aliasTypeArguments)}` : "") : ""; } // This function is used to propagate certain flags when creating new object type references and union types. // It is only necessary to do so if a constituent type might be the undefined type, the null type, the type // of an object literal or a non-inferrable type. This is because there are operations in the type checker // that care about the presence of such types at arbitrary depth in a containing type. function getPropagatingFlagsOfTypes(types: readonly Type[], excludeKinds?: TypeFlags): ObjectFlags { let result: ObjectFlags = 0; for (const type of types) { if (excludeKinds === undefined || !(type.flags & excludeKinds)) { result |= getObjectFlags(type); } } return result & ObjectFlags.PropagatingFlags; } function tryCreateTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): Type { if (some(typeArguments) && target === emptyGenericType) { return unknownType; } return createTypeReference(target, typeArguments); } function createTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): TypeReference { const id = getTypeListId(typeArguments); let type = target.instantiations.get(id); if (!type) { type = createObjectType(ObjectFlags.Reference, target.symbol) as TypeReference; target.instantiations.set(id, type); type.objectFlags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments) : 0; type.target = target; type.resolvedTypeArguments = typeArguments; } return type; } function cloneTypeReference(source: TypeReference): TypeReference { const type = createTypeWithSymbol(source.flags, source.symbol) as TypeReference; type.objectFlags = source.objectFlags; type.target = source.target; type.resolvedTypeArguments = source.resolvedTypeArguments; return type; } function createDeferredTypeReference(target: GenericType, node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, mapper?: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): DeferredTypeReference { if (!aliasSymbol) { aliasSymbol = getAliasSymbolForTypeNode(node); const localAliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); aliasTypeArguments = mapper ? instantiateTypes(localAliasTypeArguments, mapper) : localAliasTypeArguments; } const type = createObjectType(ObjectFlags.Reference, target.symbol) as DeferredTypeReference; type.target = target; type.node = node; type.mapper = mapper; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; return type; } function getTypeArguments(type: TypeReference): readonly Type[] { if (!type.resolvedTypeArguments) { if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) { return concatenate(type.target.outerTypeParameters, type.target.localTypeParameters?.map(() => errorType)) || emptyArray; } const node = type.node; const typeArguments = !node ? emptyArray : node.kind === SyntaxKind.TypeReference ? concatenate(type.target.outerTypeParameters, getEffectiveTypeArguments(node, type.target.localTypeParameters!)) : node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : map(node.elements, getTypeFromTypeNode); if (popTypeResolution()) { type.resolvedTypeArguments ??= type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments; } else { type.resolvedTypeArguments ??= concatenate(type.target.outerTypeParameters, type.target.localTypeParameters?.map(() => errorType) || emptyArray); error( type.node || currentNode, type.target.symbol ? Diagnostics.Type_arguments_for_0_circularly_reference_themselves : Diagnostics.Tuple_type_arguments_circularly_reference_themselves, type.target.symbol && symbolToString(type.target.symbol), ); } } return type.resolvedTypeArguments; } function getTypeReferenceArity(type: TypeReference): number { return length(type.target.typeParameters); } /** * Get type from type-reference that reference to class or interface */ function getTypeFromClassOrInterfaceReference(node: NodeWithTypeArguments, symbol: Symbol): Type { const type = getDeclaredTypeOfSymbol(getMergedSymbol(symbol)) as InterfaceType; const typeParameters = type.localTypeParameters; if (typeParameters) { const numTypeArguments = length(node.typeArguments); const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); const isJs = isInJSFile(node); const isJsImplicitAny = !noImplicitAny && isJs; if (!isJsImplicitAny && (numTypeArguments typeParameters.length)) { const missingAugmentsTag = isJs && isExpressionWithTypeArguments(node) && !isJSDocAugmentsTag(node.parent); const diag = minTypeArgumentCount === typeParameters.length ? missingAugmentsTag ? Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag : Diagnostics.Generic_type_0_requires_1_type_argument_s : missingAugmentsTag ? Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag : Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType); error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length); if (!isJs) { // TODO: Adopt same permissive behavior in TS as in JS to reduce follow-on editing experience failures (requires editing fillMissingTypeArguments) return errorType; } } if (node.kind === SyntaxKind.TypeReference && isDeferredTypeReferenceNode(node as TypeReferenceNode, length(node.typeArguments) !== typeParameters.length)) { return createDeferredTypeReference(type as GenericType, node as TypeReferenceNode, /*mapper*/ undefined); } // In a type reference, the outer type parameters of the referenced class or interface are automatically // supplied as type arguments and the type reference only specifies arguments for the local type parameters // of the class or interface. const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgumentsFromTypeReferenceNode(node), typeParameters, minTypeArgumentCount, isJs)); return createTypeReference(type as GenericType, typeArguments); } return checkNoTypeArguments(node, symbol) ? type : errorType; } function getTypeAliasInstantiation(symbol: Symbol, typeArguments: readonly Type[] | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { const type = getDeclaredTypeOfSymbol(symbol); if (type === intrinsicMarkerType) { const typeKind = intrinsicTypeKinds.get(symbol.escapedName as string); if (typeKind !== undefined && typeArguments && typeArguments.length === 1) { return typeKind === IntrinsicTypeKind.NoInfer ? getNoInferType(typeArguments[0]) : getStringMappingType(symbol, typeArguments[0]); } } const links = getSymbolLinks(symbol); const typeParameters = links.typeParameters!; const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); let instantiation = links.instantiations!.get(id); if (!instantiation) { links.instantiations!.set(id, instantiation = instantiateTypeWithAlias(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(symbol.valueDeclaration))), aliasSymbol, aliasTypeArguments)); } return instantiation; } /** * Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include * references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the * declared type. Instantiations are cached using the type identities of the type arguments as the key. */ function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol): Type { if (getCheckFlags(symbol) & CheckFlags.Unresolved) { const typeArguments = typeArgumentsFromTypeReferenceNode(node); const id = getAliasId(symbol, typeArguments); let errorType = errorTypes.get(id); if (!errorType) { errorType = createIntrinsicType(TypeFlags.Any, "error", /*objectFlags*/ undefined, `alias ${id}`); errorType.aliasSymbol = symbol; errorType.aliasTypeArguments = typeArguments; errorTypes.set(id, errorType); } return errorType; } const type = getDeclaredTypeOfSymbol(symbol); const typeParameters = getSymbolLinks(symbol).typeParameters; if (typeParameters) { const numTypeArguments = length(node.typeArguments); const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); if (numTypeArguments typeParameters.length) { error( node, minTypeArgumentCount === typeParameters.length ? Diagnostics.Generic_type_0_requires_1_type_argument_s : Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, symbolToString(symbol), minTypeArgumentCount, typeParameters.length, ); return errorType; } // We refrain from associating a local type alias with an instantiation of a top-level type alias // because the local alias may end up being referenced in an inferred return type where it is not // accessible--which in turn may lead to a large structural expansion of the type when generating // a .d.ts file. See #43622 for an example. const aliasSymbol = getAliasSymbolForTypeNode(node); let newAliasSymbol = aliasSymbol && (isLocalTypeAlias(symbol) || !isLocalTypeAlias(aliasSymbol)) ? aliasSymbol : undefined; let aliasTypeArguments: Type[] | undefined; if (newAliasSymbol) { aliasTypeArguments = getTypeArgumentsForAliasSymbol(newAliasSymbol); } else if (isTypeReferenceType(node)) { const aliasSymbol = resolveTypeReferenceName(node, SymbolFlags.Alias, /*ignoreErrors*/ true); // refers to an alias import/export/reexport - by making sure we use the target as an aliasSymbol, // we ensure the exported symbol is used to refer to the type when it's reserialized later if (aliasSymbol && aliasSymbol !== unknownSymbol) { const resolved = resolveAlias(aliasSymbol); if (resolved && resolved.flags & SymbolFlags.TypeAlias) { newAliasSymbol = resolved; aliasTypeArguments = typeArgumentsFromTypeReferenceNode(node) || (typeParameters ? [] : undefined); } } } return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node), newAliasSymbol, aliasTypeArguments); } return checkNoTypeArguments(node, symbol) ? type : errorType; } function isLocalTypeAlias(symbol: Symbol) { const declaration = symbol.declarations?.find(isTypeAlias); return !!(declaration && getContainingFunction(declaration)); } function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined { switch (node.kind) { case SyntaxKind.TypeReference: return node.typeName; case SyntaxKind.ExpressionWithTypeArguments: // We only support expressions that are simple qualified names. For other // expressions this produces undefined. const expr = node.expression; if (isEntityNameExpression(expr)) { return expr; } // fall through; } return undefined; } function getSymbolPath(symbol: Symbol): string { return symbol.parent ? `${getSymbolPath(symbol.parent)}.${symbol.escapedName}` : symbol.escapedName as string; } function getUnresolvedSymbolForEntityName(name: EntityNameOrEntityNameExpression) { const identifier = name.kind === SyntaxKind.QualifiedName ? name.right : name.kind === SyntaxKind.PropertyAccessExpression ? name.name : name; const text = identifier.escapedText; if (text) { const parentSymbol = name.kind === SyntaxKind.QualifiedName ? getUnresolvedSymbolForEntityName(name.left) : name.kind === SyntaxKind.PropertyAccessExpression ? getUnresolvedSymbolForEntityName(name.expression) : undefined; const path = parentSymbol ? `${getSymbolPath(parentSymbol)}.${text}` : text as string; let result = unresolvedSymbols.get(path); if (!result) { unresolvedSymbols.set(path, result = createSymbol(SymbolFlags.TypeAlias, text, CheckFlags.Unresolved)); result.parent = parentSymbol; result.links.declaredType = unresolvedType; } return result; } return unknownSymbol; } function resolveTypeReferenceName(typeReference: TypeReferenceType, meaning: SymbolFlags, ignoreErrors?: boolean) { const name = getTypeReferenceName(typeReference); if (!name) { return unknownSymbol; } const symbol = resolveEntityName(name, meaning, ignoreErrors); return symbol && symbol !== unknownSymbol ? symbol : ignoreErrors ? unknownSymbol : getUnresolvedSymbolForEntityName(name); } function getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { if (symbol === unknownSymbol) { return errorType; } symbol = getExpandoSymbol(symbol) || symbol; if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { return getTypeFromClassOrInterfaceReference(node, symbol); } if (symbol.flags & SymbolFlags.TypeAlias) { return getTypeFromTypeAliasReference(node, symbol); } // Get type from reference to named type that cannot be generic (enum or type parameter) const res = tryGetDeclaredTypeOfSymbol(symbol); if (res) { return checkNoTypeArguments(node, symbol) ? getRegularTypeOfLiteralType(res) : errorType; } if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { const jsdocType = getTypeFromJSDocValueReference(node, symbol); if (jsdocType) { return jsdocType; } else { // Resolve the type reference as a Type for the purpose of reporting errors. resolveTypeReferenceName(node, SymbolFlags.Type); return getTypeOfSymbol(symbol); } } return errorType; } /** * A JSdoc TypeReference may be to a value, but resolve it as a type anyway. * Example: import('./b').ConstructorFunction */ function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined { const links = getNodeLinks(node); if (!links.resolvedJSDocType) { const valueType = getTypeOfSymbol(symbol); let typeType = valueType; if (symbol.valueDeclaration) { const isImportTypeWithQualifier = node.kind === SyntaxKind.ImportType && (node as ImportTypeNode).qualifier; // valueType might not have a symbol, eg, {import('./b').STRING_LITERAL} if (valueType.symbol && valueType.symbol !== symbol && isImportTypeWithQualifier) { typeType = getTypeReferenceType(node, valueType.symbol); } } links.resolvedJSDocType = typeType; } return links.resolvedJSDocType; } function getNoInferType(type: Type) { return isNoInferTargetType(type) ? getOrCreateSubstitutionType(type, unknownType) : type; } function isNoInferTargetType(type: Type): boolean { // This is effectively a more conservative and predictable form of couldContainTypeVariables. We want to // preserve NoInfer only for types that could contain type variables, but we don't want to exhaustively // examine all object type members. return !!(type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, isNoInferTargetType) || type.flags & TypeFlags.Substitution && !isNoInferType(type) && isNoInferTargetType((type as SubstitutionType).baseType) || type.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(type) || type.flags & (TypeFlags.Instantiable & ~TypeFlags.Substitution) && !isPatternLiteralType(type)); } function isNoInferType(type: Type) { // A NoInfer type is represented as a substitution type with a TypeFlags.Unknown constraint. return !!(type.flags & TypeFlags.Substitution && (type as SubstitutionType).constraint.flags & TypeFlags.Unknown); } function getSubstitutionType(baseType: Type, constraint: Type) { return constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || baseType.flags & TypeFlags.Any ? baseType : getOrCreateSubstitutionType(baseType, constraint); } function getOrCreateSubstitutionType(baseType: Type, constraint: Type) { const id = `${getTypeId(baseType)}>${getTypeId(constraint)}`; const cached = substitutionTypes.get(id); if (cached) { return cached; } const result = createType(TypeFlags.Substitution) as SubstitutionType; result.baseType = baseType; result.constraint = constraint; substitutionTypes.set(id, result); return result; } function getSubstitutionIntersection(substitutionType: SubstitutionType) { return isNoInferType(substitutionType) ? substitutionType.baseType : getIntersectionType([substitutionType.constraint, substitutionType.baseType]); } function isUnaryTupleTypeNode(node: TypeNode) { return node.kind === SyntaxKind.TupleType && (node as TupleTypeNode).elements.length === 1; } function getImpliedConstraint(type: Type, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(type, (checkNode as TupleTypeNode).elements[0], (extendsNode as TupleTypeNode).elements[0]) : getActualTypeVariable(getTypeFromTypeNode(checkNode)) === getActualTypeVariable(type) ? getTypeFromTypeNode(extendsNode) : undefined; } function getConditionalFlowTypeOfType(type: Type, node: Node) { let constraints: Type[] | undefined; let covariant = true; while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDoc) { const parent = node.parent; // only consider variance flipped by parameter locations - `keyof` types would usually be considered variance inverting, but // often get used in indexed accesses where they behave sortof invariantly, but our checking is lax if (parent.kind === SyntaxKind.Parameter) { covariant = !covariant; } // Always substitute on type parameters, regardless of variance, since even // in contravariant positions, they may rely on substituted constraints to be valid if ((covariant || type.flags & TypeFlags.TypeVariable) && parent.kind === SyntaxKind.ConditionalType && node === (parent as ConditionalTypeNode).trueType) { const constraint = getImpliedConstraint(type, (parent as ConditionalTypeNode).checkType, (parent as ConditionalTypeNode).extendsType); if (constraint) { constraints = append(constraints, constraint); } } // Given a homomorphic mapped type { [K in keyof T]: XXX }, where T is constrained to an array or tuple type, in the // template type XXX, K has an added constraint of number | `${number}`. else if (type.flags & TypeFlags.TypeParameter && parent.kind === SyntaxKind.MappedType && !(parent as MappedTypeNode).nameType && node === (parent as MappedTypeNode).type) { const mappedType = getTypeFromTypeNode(parent as TypeNode) as MappedType; if (getTypeParameterFromMappedType(mappedType) === getActualTypeVariable(type)) { const typeParameter = getHomomorphicTypeVariable(mappedType); if (typeParameter) { const constraint = getConstraintOfTypeParameter(typeParameter); if (constraint && everyType(constraint, isArrayOrTupleType)) { constraints = append(constraints, getUnionType([numberType, numericStringType])); } } } } node = parent; } return constraints ? getSubstitutionType(type, getIntersectionType(constraints)) : type; } function isJSDocTypeReference(node: Node): node is TypeReferenceNode { return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); } function checkNoTypeArguments(node: NodeWithTypeArguments, symbol?: Symbol) { if (node.typeArguments) { error(node, Diagnostics.Type_0_is_not_generic, symbol ? symbolToString(symbol) : (node as TypeReferenceNode).typeName ? declarationNameToString((node as TypeReferenceNode).typeName) : anon); return false; } return true; } function getIntendedTypeFromJSDocTypeReference(node: TypeReferenceNode): Type | undefined { if (isIdentifier(node.typeName)) { const typeArgs = node.typeArguments; switch (node.typeName.escapedText) { case "String": checkNoTypeArguments(node); return stringType; case "Number": checkNoTypeArguments(node); return numberType; case "BigInt": checkNoTypeArguments(node); return bigintType; case "Boolean": checkNoTypeArguments(node); return booleanType; case "Void": checkNoTypeArguments(node); return voidType; case "Undefined": checkNoTypeArguments(node); return undefinedType; case "Null": checkNoTypeArguments(node); return nullType; case "Function": case "function": checkNoTypeArguments(node); return globalFunctionType; case "array": return (!typeArgs || !typeArgs.length) && !noImplicitAny ? anyArrayType : undefined; case "promise": return (!typeArgs || !typeArgs.length) && !noImplicitAny ? createPromiseType(anyType) : undefined; case "Object": if (typeArgs && typeArgs.length === 2) { if (isJSDocIndexSignature(node)) { const indexed = getTypeFromTypeNode(typeArgs[0]); const target = getTypeFromTypeNode(typeArgs[1]); const indexInfo = indexed === stringType || indexed === numberType ? [createIndexInfo(indexed, target, /*isReadonly*/ false)] : emptyArray; return createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, indexInfo); } return anyType; } checkNoTypeArguments(node); return !noImplicitAny ? anyType : undefined; } } } function getTypeFromJSDocNullableTypeNode(node: JSDocNullableType) { const type = getTypeFromTypeNode(node.type); return strictNullChecks ? getNullableType(type, TypeFlags.Null) : type; } function getTypeFromTypeReference(node: TypeReferenceType): Type { const links = getNodeLinks(node); if (!links.resolvedType) { // handle LS queries on the `const` in `x as const` by resolving to the type of `x` if (isConstTypeReference(node) && isAssertionExpression(node.parent)) { links.resolvedSymbol = unknownSymbol; return links.resolvedType = checkExpressionCached(node.parent.expression); } let symbol: Symbol | undefined; let type: Type | undefined; const meaning = SymbolFlags.Type; if (isJSDocTypeReference(node)) { type = getIntendedTypeFromJSDocTypeReference(node); if (!type) { symbol = resolveTypeReferenceName(node, meaning, /*ignoreErrors*/ true); if (symbol === unknownSymbol) { symbol = resolveTypeReferenceName(node, meaning | SymbolFlags.Value); } else { resolveTypeReferenceName(node, meaning); // Resolve again to mark errors, if any } type = getTypeReferenceType(node, symbol); } } if (!type) { symbol = resolveTypeReferenceName(node, meaning); type = getTypeReferenceType(node, symbol); } // Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the // type reference in checkTypeReferenceNode. links.resolvedSymbol = symbol; links.resolvedType = type; } return links.resolvedType; } function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined { return map(node.typeArguments, getTypeFromTypeNode); } function getTypeFromTypeQueryNode(node: TypeQueryNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { // TypeScript 1.0 spec (April 2014): 3.6.3 // The expression is processed as an identifier expression (section 4.3) // or property access expression(section 4.10), // the widened type(section 3.9) of which becomes the result. const type = checkExpressionWithTypeArguments(node); links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(type)); } return links.resolvedType; } function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType { function getTypeDeclaration(symbol: Symbol): Declaration | undefined { const declarations = symbol.declarations; if (declarations) { for (const declaration of declarations) { switch (declaration.kind) { case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: return declaration; } } } } if (!symbol) { return arity ? emptyGenericType : emptyObjectType; } const type = getDeclaredTypeOfSymbol(symbol); if (!(type.flags & TypeFlags.Object)) { error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol)); return arity ? emptyGenericType : emptyObjectType; } if (length((type as InterfaceType).typeParameters) !== arity) { error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); return arity ? emptyGenericType : emptyObjectType; } return type as ObjectType; } function getGlobalValueSymbol(name: __String, reportErrors: boolean): Symbol | undefined { return getGlobalSymbol(name, SymbolFlags.Value, reportErrors ? Diagnostics.Cannot_find_global_value_0 : undefined); } function getGlobalTypeSymbol(name: __String, reportErrors: boolean): Symbol | undefined { return getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); } function getGlobalTypeAliasSymbol(name: __String, arity: number, reportErrors: boolean): Symbol | undefined { const symbol = getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); if (symbol) { // Resolve the declared type of the symbol. This resolves type parameters for the type // alias so that we can check arity. getDeclaredTypeOfSymbol(symbol); if (length(getSymbolLinks(symbol).typeParameters) !== arity) { const decl = symbol.declarations && find(symbol.declarations, isTypeAliasDeclaration); error(decl, Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); return undefined; } } return symbol; } function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): Symbol | undefined { // Don't track references for global symbols anyway, so value if `isReference` is arbitrary return resolveName(/*location*/ undefined, name, meaning, diagnostic, /*isUse*/ false, /*excludeGlobals*/ false); } function getGlobalType(name: __String, arity: 0, reportErrors: true): ObjectType; function getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType | undefined; function getGlobalType(name: __String, arity: number, reportErrors: true): GenericType; function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType | undefined; function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined { const symbol = getGlobalTypeSymbol(name, reportErrors); return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined; } function getGlobalBuiltinTypes(typeNames: readonly string[], arity: 0): ObjectType[]; function getGlobalBuiltinTypes(typeNames: readonly string[], arity: number): GenericType[]; function getGlobalBuiltinTypes(typeNames: readonly string[], arity: number) { let types: Type[] | undefined; for (const typeName of typeNames) { types = append(types, getGlobalType(typeName as __String, arity, /*reportErrors*/ false)); } return types ?? emptyArray; } function getGlobalTypedPropertyDescriptorType() { // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times return deferredGlobalTypedPropertyDescriptorType ||= getGlobalType("TypedPropertyDescriptor" as __String, /*arity*/ 1, /*reportErrors*/ true) || emptyGenericType; } function getGlobalTemplateStringsArrayType() { // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times return deferredGlobalTemplateStringsArrayType ||= getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; } function getGlobalImportMetaType() { // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times return deferredGlobalImportMetaType ||= getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; } function getGlobalImportMetaExpressionType() { if (!deferredGlobalImportMetaExpressionType) { // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` const symbol = createSymbol(SymbolFlags.None, "ImportMetaExpression" as __String); const importMetaType = getGlobalImportMetaType(); const metaPropertySymbol = createSymbol(SymbolFlags.Property, "meta" as __String, CheckFlags.Readonly); metaPropertySymbol.parent = symbol; metaPropertySymbol.links.type = importMetaType; const members = createSymbolTable([metaPropertySymbol]); symbol.members = members; deferredGlobalImportMetaExpressionType = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); } return deferredGlobalImportMetaExpressionType; } function getGlobalImportCallOptionsType(reportErrors: boolean) { return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } function getGlobalImportAttributesType(reportErrors: boolean) { return (deferredGlobalImportAttributesType ||= getGlobalType("ImportAttributes" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined { return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors); } function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean): Symbol | undefined { return deferredGlobalESSymbolConstructorTypeSymbol ||= getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors); } function getGlobalESSymbolType() { return (deferredGlobalESSymbolType ||= getGlobalType("Symbol" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; } function getGlobalPromiseType(reportErrors: boolean) { return (deferredGlobalPromiseType ||= getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalPromiseLikeType(reportErrors: boolean) { return (deferredGlobalPromiseLikeType ||= getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { return deferredGlobalPromiseConstructorSymbol ||= getGlobalValueSymbol("Promise" as __String, reportErrors); } function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { return (deferredGlobalPromiseConstructorLikeType ||= getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } function getGlobalAsyncIterableType(reportErrors: boolean) { return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalAsyncIteratorType(reportErrors: boolean) { return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalBuiltinAsyncIteratorTypes() { // NOTE: This list does not include all built-in async iterator types, only those that are likely to be encountered frequently. return deferredGlobalBuiltinAsyncIteratorTypes ??= getGlobalBuiltinTypes(["ReadableStreamAsyncIterator"], 1); } function getGlobalAsyncIteratorObjectType(reportErrors: boolean) { return (deferredGlobalAsyncIteratorObjectType ||= getGlobalType("AsyncIteratorObject" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalAsyncGeneratorType(reportErrors: boolean) { return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIterableType(reportErrors: boolean) { return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIteratorType(reportErrors: boolean) { return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIterableIteratorType(reportErrors: boolean) { return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getBuiltinIteratorReturnType() { return strictBuiltinIteratorReturn ? undefinedType : anyType; } function getGlobalBuiltinIteratorTypes() { // NOTE: This list does not include all built-in iterator types, only those that are likely to be encountered frequently. return deferredGlobalBuiltinIteratorTypes ??= getGlobalBuiltinTypes(["ArrayIterator", "MapIterator", "SetIterator", "StringIterator"], 1); } function getGlobalIteratorObjectType(reportErrors: boolean) { return (deferredGlobalIteratorObjectType ||= getGlobalType("IteratorObject" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalGeneratorType(reportErrors: boolean) { return (deferredGlobalGeneratorType ||= getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIteratorYieldResultType(reportErrors: boolean) { return (deferredGlobalIteratorYieldResultType ||= getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalIteratorReturnResultType(reportErrors: boolean) { return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalDisposableType(reportErrors: boolean) { return (deferredGlobalDisposableType ||= getGlobalType("Disposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } function getGlobalAsyncDisposableType(reportErrors: boolean) { return (deferredGlobalAsyncDisposableType ||= getGlobalType("AsyncDisposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType; } function getGlobalExtractSymbol(): Symbol | undefined { // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times deferredGlobalExtractSymbol ||= getGlobalTypeAliasSymbol("Extract" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; return deferredGlobalExtractSymbol === unknownSymbol ? undefined : deferredGlobalExtractSymbol; } function getGlobalOmitSymbol(): Symbol | undefined { // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times deferredGlobalOmitSymbol ||= getGlobalTypeAliasSymbol("Omit" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; return deferredGlobalOmitSymbol === unknownSymbol ? undefined : deferredGlobalOmitSymbol; } function getGlobalAwaitedSymbol(reportErrors: boolean): Symbol | undefined { // Only cache `unknownSymbol` if we are reporting errors so that we don't report the error more than once. deferredGlobalAwaitedSymbol ||= getGlobalTypeAliasSymbol("Awaited" as __String, /*arity*/ 1, reportErrors) || (reportErrors ? unknownSymbol : undefined); return deferredGlobalAwaitedSymbol === unknownSymbol ? undefined : deferredGlobalAwaitedSymbol; } function getGlobalBigIntType() { return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; } function getGlobalClassDecoratorContextType(reportErrors: boolean) { return (deferredGlobalClassDecoratorContextType ??= getGlobalType("ClassDecoratorContext" as __String, /*arity*/ 1, reportErrors)) ?? emptyGenericType; } function getGlobalClassMethodDecoratorContextType(reportErrors: boolean) { return (deferredGlobalClassMethodDecoratorContextType ??= getGlobalType("ClassMethodDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; } function getGlobalClassGetterDecoratorContextType(reportErrors: boolean) { return (deferredGlobalClassGetterDecoratorContextType ??= getGlobalType("ClassGetterDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; } function getGlobalClassSetterDecoratorContextType(reportErrors: boolean) { return (deferredGlobalClassSetterDecoratorContextType ??= getGlobalType("ClassSetterDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; } function getGlobalClassAccessorDecoratorContextType(reportErrors: boolean) { return (deferredGlobalClassAccessorDecoratorContextType ??= getGlobalType("ClassAccessorDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; } function getGlobalClassAccessorDecoratorTargetType(reportErrors: boolean) { return (deferredGlobalClassAccessorDecoratorTargetType ??= getGlobalType("ClassAccessorDecoratorTarget" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; } function getGlobalClassAccessorDecoratorResultType(reportErrors: boolean) { return (deferredGlobalClassAccessorDecoratorResultType ??= getGlobalType("ClassAccessorDecoratorResult" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; } function getGlobalClassFieldDecoratorContextType(reportErrors: boolean) { return (deferredGlobalClassFieldDecoratorContextType ??= getGlobalType("ClassFieldDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; } function getGlobalNaNSymbol(): Symbol | undefined { return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false)); } function getGlobalRecordSymbol(): Symbol | undefined { deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol; } /** * Instantiates a global type that is generic with some element type, and returns that instantiation. */ function createTypeFromGenericGlobalType(genericGlobalType: GenericType, typeArguments: readonly Type[]): ObjectType { return genericGlobalType !== emptyGenericType ? createTypeReference(genericGlobalType, typeArguments) : emptyObjectType; } function createTypedPropertyDescriptorType(propertyType: Type): Type { return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); } function createIterableType(iteratedType: Type): Type { return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType, voidType, undefinedType]); } function createArrayType(elementType: Type, readonly?: boolean): ObjectType { return createTypeFromGenericGlobalType(readonly ? globalReadonlyArrayType : globalArrayType, [elementType]); } function getTupleElementFlags(node: TypeNode) { switch (node.kind) { case SyntaxKind.OptionalType: return ElementFlags.Optional; case SyntaxKind.RestType: return getRestTypeElementFlags(node as RestTypeNode); case SyntaxKind.NamedTupleMember: return (node as NamedTupleMember).questionToken ? ElementFlags.Optional : (node as NamedTupleMember).dotDotDotToken ? getRestTypeElementFlags(node as NamedTupleMember) : ElementFlags.Required; default: return ElementFlags.Required; } } function getRestTypeElementFlags(node: RestTypeNode | NamedTupleMember) { return getArrayElementTypeNode(node.type) ? ElementFlags.Rest : ElementFlags.Variadic; } function getArrayOrTupleTargetType(node: ArrayTypeNode | TupleTypeNode): GenericType { const readonly = isReadonlyTypeOperator(node.parent); const elementType = getArrayElementTypeNode(node); if (elementType) { return readonly ? globalReadonlyArrayType : globalArrayType; } const elementFlags = map((node as TupleTypeNode).elements, getTupleElementFlags); return getTupleTargetType(elementFlags, readonly, map((node as TupleTypeNode).elements, memberIfLabeledElementDeclaration)); } function memberIfLabeledElementDeclaration(member: Node): NamedTupleMember | ParameterDeclaration | undefined { return isNamedTupleMember(member) || isParameter(member) ? member : undefined; } // Return true if the given type reference node is directly aliased or if it needs to be deferred // because it is possibly contained in a circular chain of eagerly resolved types. function isDeferredTypeReferenceNode(node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, hasDefaultTypeArguments?: boolean) { return !!getAliasSymbolForTypeNode(node) || isResolvedByTypeAlias(node) && ( node.kind === SyntaxKind.ArrayType ? mayResolveTypeAlias(node.elementType) : node.kind === SyntaxKind.TupleType ? some(node.elements, mayResolveTypeAlias) : hasDefaultTypeArguments || some(node.typeArguments, mayResolveTypeAlias) ); } // Return true when the given node is transitively contained in type constructs that eagerly // resolve their constituent types. We include SyntaxKind.TypeReference because type arguments // of type aliases are eagerly resolved. function isResolvedByTypeAlias(node: Node): boolean { const parent = node.parent; switch (parent.kind) { case SyntaxKind.ParenthesizedType: case SyntaxKind.NamedTupleMember: case SyntaxKind.TypeReference: case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: case SyntaxKind.IndexedAccessType: case SyntaxKind.ConditionalType: case SyntaxKind.TypeOperator: case SyntaxKind.ArrayType: case SyntaxKind.TupleType: return isResolvedByTypeAlias(parent); case SyntaxKind.TypeAliasDeclaration: return true; } return false; } // Return true if resolving the given node (i.e. getTypeFromTypeNode) possibly causes resolution // of a type alias. function mayResolveTypeAlias(node: Node): boolean { switch (node.kind) { case SyntaxKind.TypeReference: return isJSDocTypeReference(node) || !!(resolveTypeReferenceName(node as TypeReferenceNode, SymbolFlags.Type).flags & SymbolFlags.TypeAlias); case SyntaxKind.TypeQuery: return true; case SyntaxKind.TypeOperator: return (node as TypeOperatorNode).operator !== SyntaxKind.UniqueKeyword && mayResolveTypeAlias((node as TypeOperatorNode).type); case SyntaxKind.ParenthesizedType: case SyntaxKind.OptionalType: case SyntaxKind.NamedTupleMember: case SyntaxKind.JSDocOptionalType: case SyntaxKind.JSDocNullableType: case SyntaxKind.JSDocNonNullableType: case SyntaxKind.JSDocTypeExpression: return mayResolveTypeAlias((node as ParenthesizedTypeNode | OptionalTypeNode | JSDocTypeReferencingNode | NamedTupleMember).type); case SyntaxKind.RestType: return (node as RestTypeNode).type.kind !== SyntaxKind.ArrayType || mayResolveTypeAlias(((node as RestTypeNode).type as ArrayTypeNode).elementType); case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: return some((node as UnionOrIntersectionTypeNode).types, mayResolveTypeAlias); case SyntaxKind.IndexedAccessType: return mayResolveTypeAlias((node as IndexedAccessTypeNode).objectType) || mayResolveTypeAlias((node as IndexedAccessTypeNode).indexType); case SyntaxKind.ConditionalType: return mayResolveTypeAlias((node as ConditionalTypeNode).checkType) || mayResolveTypeAlias((node as ConditionalTypeNode).extendsType) || mayResolveTypeAlias((node as ConditionalTypeNode).trueType) || mayResolveTypeAlias((node as ConditionalTypeNode).falseType); } return false; } function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const target = getArrayOrTupleTargetType(node); if (target === emptyGenericType) { links.resolvedType = emptyObjectType; } else if (!(node.kind === SyntaxKind.TupleType && some(node.elements, e => !!(getTupleElementFlags(e) & ElementFlags.Variadic))) && isDeferredTypeReferenceNode(node)) { links.resolvedType = node.kind === SyntaxKind.TupleType && node.elements.length === 0 ? target : createDeferredTypeReference(target, node, /*mapper*/ undefined); } else { const elementTypes = node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : map(node.elements, getTypeFromTypeNode); links.resolvedType = createNormalizedTypeReference(target, elementTypes); } } return links.resolvedType; } function isReadonlyTypeOperator(node: Node) { return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword; } function createTupleType(elementTypes: readonly Type[], elementFlags?: readonly ElementFlags[], readonly = false, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration | undefined)[] = []) { const tupleTarget = getTupleTargetType(elementFlags || map(elementTypes, _ => ElementFlags.Required), readonly, namedMemberDeclarations); return tupleTarget === emptyGenericType ? emptyObjectType : elementTypes.length ? createNormalizedTypeReference(tupleTarget, elementTypes) : tupleTarget; } function getTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration | undefined)[]): GenericType { if (elementFlags.length === 1 && elementFlags[0] & ElementFlags.Rest) { // [...X[]] is equivalent to just X[] return readonly ? globalReadonlyArrayType : globalArrayType; } const key = map(elementFlags, f => f & ElementFlags.Required ? "#" : f & ElementFlags.Optional ? "?" : f & ElementFlags.Rest ? "." : "*").join() + (readonly ? "R" : "") + (some(namedMemberDeclarations, node => !!node) ? "," + map(namedMemberDeclarations, node => node ? getNodeId(node) : "_").join(",") : ""); let type = tupleTypes.get(key); if (!type) { tupleTypes.set(key, type = createTupleTargetType(elementFlags, readonly, namedMemberDeclarations)); } return type; } // We represent tuple types as type references to synthesized generic interface types created by // this function. The types are of the form: // // interface Tuple extends Array { 0: T0, 1: T1, 2: T2, ... } // // Note that the generic type created by this function has no symbol associated with it. The same // is true for each of the synthesized type parameters. function createTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration | undefined)[]): TupleType { const arity = elementFlags.length; const minLength = countWhere(elementFlags, f => !!(f & (ElementFlags.Required | ElementFlags.Variadic))); let typeParameters: TypeParameter[] | undefined; const properties: Symbol[] = []; let combinedFlags = 0 as ElementFlags; if (arity) { typeParameters = new Array(arity); for (let i = 0; i (); type.instantiations.set(getTypeListId(type.typeParameters), type as GenericType); type.target = type as GenericType; type.resolvedTypeArguments = type.typeParameters; type.thisType = createTypeParameter(); type.thisType.isThisType = true; type.thisType.constraint = type; type.declaredProperties = properties; type.declaredCallSignatures = emptyArray; type.declaredConstructSignatures = emptyArray; type.declaredIndexInfos = emptyArray; type.elementFlags = elementFlags; type.minLength = minLength; type.fixedLength = fixedLength; type.hasRestElement = !!(combinedFlags & ElementFlags.Variable); type.combinedFlags = combinedFlags; type.readonly = readonly; type.labeledElementDeclarations = namedMemberDeclarations; return type; } function createNormalizedTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined) { return target.objectFlags & ObjectFlags.Tuple ? createNormalizedTupleType(target as TupleType, typeArguments!) : createTypeReference(target, typeArguments); } function createNormalizedTupleType(target: TupleType, elementTypes: readonly Type[]): Type { if (!(target.combinedFlags & ElementFlags.NonRequired)) { // No need to normalize when we only have regular required elements return createTypeReference(target, elementTypes); } if (target.combinedFlags & ElementFlags.Variadic) { // Transform [A, ...(X | Y | Z)] into [A, ...X] | [A, ...Y] | [A, ...Z] const unionIndex = findIndex(elementTypes, (t, i) => !!(target.elementFlags[i] & ElementFlags.Variadic && t.flags & (TypeFlags.Never | TypeFlags.Union))); if (unionIndex >= 0) { return checkCrossProductUnion(map(elementTypes, (t, i) => target.elementFlags[i] & ElementFlags.Variadic ? t : unknownType)) ? mapType(elementTypes[unionIndex], t => createNormalizedTupleType(target, replaceElement(elementTypes, unionIndex, t))) : errorType; } } // We have optional, rest, or variadic elements that may need normalizing. Normalization ensures that all variadic // elements are generic and that the tuple type has one of the following layouts, disregarding variadic elements: // (1) Zero or more required elements, followed by zero or more optional elements, followed by zero or one rest element. // (2) Zero or more required elements, followed by a rest element, followed by zero or more required elements. // In either layout, zero or more generic variadic elements may be present at any location. const expandedTypes: Type[] = []; const expandedFlags: ElementFlags[] = []; const expandedDeclarations: (NamedTupleMember | ParameterDeclaration | undefined)[] = []; let lastRequiredIndex = -1; let firstRestIndex = -1; let lastOptionalOrRestIndex = -1; for (let i = 0; i = 10_000) { error( currentNode, isPartOfTypeNode(currentNode!) ? Diagnostics.Type_produces_a_tuple_type_that_is_too_large_to_represent : Diagnostics.Expression_produces_a_tuple_type_that_is_too_large_to_represent, ); return errorType; } // Spread variadic elements with tuple types into the resulting tuple. forEach(elements, (t, n) => addElement(t, type.target.elementFlags[n], type.target.labeledElementDeclarations?.[n])); } else { // Treat everything else as an array type and create a rest element. addElement(isArrayLikeType(type) && getIndexTypeOfType(type, numberType) || errorType, ElementFlags.Rest, target.labeledElementDeclarations?.[i]); } } else { // Copy other element kinds with no change. addElement(type, flags, target.labeledElementDeclarations?.[i]); } } // Turn optional elements preceding the last required element into required elements for (let i = 0; i = 0 && firstRestIndex expandedFlags[firstRestIndex + i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t)); expandedTypes.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); expandedFlags.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); expandedDeclarations.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); } const tupleTarget = getTupleTargetType(expandedFlags, target.readonly, expandedDeclarations); return tupleTarget === emptyGenericType ? emptyObjectType : expandedFlags.length ? createTypeReference(tupleTarget, expandedTypes) : tupleTarget; function addElement(type: Type, flags: ElementFlags, declaration: NamedTupleMember | ParameterDeclaration | undefined) { if (flags & ElementFlags.Required) { lastRequiredIndex = expandedFlags.length; } if (flags & ElementFlags.Rest && firstRestIndex target.fixedLength ? getRestArrayTypeOfTupleType(type) || createTupleType(emptyArray) : createTupleType(getTypeArguments(type).slice(index, endIndex), target.elementFlags.slice(index, endIndex), /*readonly*/ false, target.labeledElementDeclarations && target.labeledElementDeclarations.slice(index, endIndex)); } function getKnownKeysOfTupleType(type: TupleTypeReference) { return getUnionType(append(arrayOf(type.target.fixedLength, i => getStringLiteralType("" + i)), getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType))); } // Return count of starting consecutive tuple elements of the given kind(s) function getStartElementCount(type: TupleType, flags: ElementFlags) { const index = findIndex(type.elementFlags, f => !(f & flags)); return index >= 0 ? index : type.elementFlags.length; } // Return count of ending consecutive tuple elements of the given kind(s) function getEndElementCount(type: TupleType, flags: ElementFlags) { return type.elementFlags.length - findLastIndex(type.elementFlags, f => !(f & flags)) - 1; } function getTotalFixedElementCount(type: TupleType) { return type.fixedLength + getEndElementCount(type, ElementFlags.Fixed); } function getElementTypes(type: TupleTypeReference): readonly Type[] { const typeArguments = getTypeArguments(type); const arity = getTypeReferenceArity(type); return typeArguments.length === arity ? typeArguments : typeArguments.slice(0, arity); } function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true); } function getTypeId(type: Type): TypeId { return type.id; } function containsType(types: readonly Type[], type: Type): boolean { return binarySearch(types, type, getTypeId, compareValues) >= 0; } function insertType(types: Type[], type: Type): boolean { const index = binarySearch(types, type, getTypeId, compareValues); if (index typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); if (index [A]`, // where A and B are large union types. if (type !== lastType) { includes = type.flags & TypeFlags.Union ? addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types) : addTypeToUnion(typeSet, includes, type); lastType = type; } } return includes; } function removeSubtypes(types: Type[], hasObjectTypes: boolean): Type[] | undefined { // [] and [T] immediately reduce to [] and [T] respectively if (types.length !!(t.flags & TypeFlags.Object) && !isGenericMappedType(t) && isEmptyResolvedType(resolveStructuredTypeMembers(t as ObjectType))); const len = types.length; let i = len; let count = 0; while (i > 0) { i--; const source = types[i]; if (hasEmptyObject || source.flags & TypeFlags.StructuredOrInstantiable) { // A type parameter with a union constraint may be a subtype of some union, but not a subtype of the // individual constituents of that union. For example, `T extends A | B` is a subtype of `A | B`, but not // a subtype of just `A` or just `B`. When we encounter such a type parameter, we therefore check if the // type parameter is a subtype of a union of all the other types. if (source.flags & TypeFlags.TypeParameter && getBaseConstraintOrType(source).flags & TypeFlags.Union) { if (isTypeRelatedTo(source, getUnionType(map(types, t => t === source ? neverType : t)), strictSubtypeRelation)) { orderedRemoveItemAt(types, i); } continue; } // Find the first property with a unit type, if any. When constituents have a property by the same name // but of a different unit type, we can quickly disqualify them from subtype checks. This helps subtype // reduction of large discriminated union types. const keyProperty = source.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive) ? find(getPropertiesOfType(source), p => isUnitType(getTypeOfSymbol(p))) : undefined; const keyPropertyType = keyProperty && getRegularTypeOfLiteralType(getTypeOfSymbol(keyProperty)); for (const target of types) { if (source !== target) { if (count === 100000) { // After 100000 subtype checks we estimate the remaining amount of work by assuming the // same ratio of checks per element. If the estimated number of remaining type checks is // greater than 1M we deem the union type too complex to represent. This for example // caps union types at 1000 unique object types. const estimatedCount = (count / (len - i)) * len; if (estimatedCount > 1000000) { tracing?.instant(tracing.Phase.CheckTypes, "removeSubtypes_DepthLimit", { typeIds: types.map(t => t.id) }); error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); return undefined; } } count++; if (keyProperty && target.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { const t = getTypeOfPropertyOfType(target, keyProperty.escapedName); if (t && isUnitType(t) && getRegularTypeOfLiteralType(t) !== keyPropertyType) { continue; } } if ( isTypeRelatedTo(source, target, strictSubtypeRelation) && ( !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || isTypeDerivedFrom(source, target) ) ) { orderedRemoveItemAt(types, i); break; } } } } } subtypeReductionCache.set(id, types); return types; } function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags, reduceVoidUndefined: boolean) { let i = types.length; while (i > 0) { i--; const t = types[i]; const flags = t.flags; const remove = flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && includes & TypeFlags.String || flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt || flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || reduceVoidUndefined && flags & TypeFlags.Undefined && includes & TypeFlags.Void || isFreshLiteralType(t) && containsType(types, (t as LiteralType).regularType); if (remove) { orderedRemoveItemAt(types, i); } } } function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { const templates = filter(types, isPatternLiteralType) as (TemplateLiteralType | StringMappingType)[]; if (templates.length) { let i = types.length; while (i > 0) { i--; const t = types[i]; if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralOrStringMapping(t, template))) { orderedRemoveItemAt(types, i); } } } } function isTypeMatchedByTemplateLiteralOrStringMapping(type: Type, template: TemplateLiteralType | StringMappingType) { return template.flags & TypeFlags.TemplateLiteral ? isTypeMatchedByTemplateLiteralType(type, template as TemplateLiteralType) : isMemberOfStringMapping(type, template); } function removeConstrainedTypeVariables(types: Type[]) { const typeVariables: TypeVariable[] = []; // First collect a list of the type variables occurring in constraining intersections. for (const type of types) { if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; pushIfUnique(typeVariables, (type as IntersectionType).types[index]); } } // For each type variable, check if the constraining intersections for that type variable fully // cover the constraint of the type variable; if so, remove the constraining intersections and // substitute the type variable. for (const typeVariable of typeVariables) { const primitives: Type[] = []; // First collect the primitive types from the constraining intersections. for (const type of types) { if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; if ((type as IntersectionType).types[index] === typeVariable) { insertType(primitives, (type as IntersectionType).types[1 - index]); } } } // If every constituent in the type variable's constraint is covered by an intersection of the type // variable and that constituent, remove those intersections and substitute the type variable. const constraint = getBaseConstraintOfType(typeVariable)!; if (everyType(constraint, t => containsType(primitives, t))) { let i = types.length; while (i > 0) { i--; const type = types[i]; if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; if ((type as IntersectionType).types[index] === typeVariable && containsType(primitives, (type as IntersectionType).types[1 - index])) { orderedRemoveItemAt(types, i); } } } insertType(types, typeVariable); } } } function isNamedUnionType(type: Type) { return !!(type.flags & TypeFlags.Union && (type.aliasSymbol || (type as UnionType).origin)); } function addNamedUnions(namedUnions: Type[], types: readonly Type[]) { for (const t of types) { if (t.flags & TypeFlags.Union) { const origin = (t as UnionType).origin; if (t.aliasSymbol || origin && !(origin.flags & TypeFlags.Union)) { pushIfUnique(namedUnions, t); } else if (origin && origin.flags & TypeFlags.Union) { addNamedUnions(namedUnions, (origin as UnionType).types); } } } } function createOriginUnionOrIntersectionType(flags: TypeFlags, types: Type[]) { const result = createOriginType(flags) as UnionOrIntersectionType; result.types = types; return result; } // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction // flag is specified we also reduce the constituent type set to only include types that aren't subtypes // of other types. Subtype reduction is expensive for large union types and is possible only when union // types are known not to circularly reference themselves (as is the case with union types created by // expression constructs such as array literals and the || and ?: operators). Named types can // circularly reference themselves and therefore cannot be subtype reduced during their declaration. // For example, "type Item = string | (() => Item)" is a named type that circularly references itself. function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { if (types.length === 0) { return neverType; } if (types.length === 1) { return types[0]; } // We optimize for the common case of unioning a union type with some other type (such as `undefined`). if (types.length === 2 && !origin && (types[0].flags & TypeFlags.Union || types[1].flags & TypeFlags.Union)) { const infix = unionReduction === UnionReduction.None ? "N" : unionReduction === UnionReduction.Subtype ? "S" : "L"; const index = types[0].id = 2 && typeSet[0] === undefinedType && typeSet[1] === missingType) { orderedRemoveItemAt(typeSet, 1); } } if (includes & (TypeFlags.Enum | TypeFlags.Literal | TypeFlags.UniqueESSymbol | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) { removeRedundantLiteralTypes(typeSet, includes, !!(unionReduction & UnionReduction.Subtype)); } if (includes & TypeFlags.StringLiteral && includes & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) { removeStringLiteralsMatchedByTemplateLiterals(typeSet); } if (includes & TypeFlags.IncludesConstrainedTypeVariable) { removeConstrainedTypeVariables(typeSet); } if (unionReduction === UnionReduction.Subtype) { typeSet = removeSubtypes(typeSet, !!(includes & TypeFlags.Object)); if (!typeSet) { return errorType; } } if (typeSet.length === 0) { return includes & TypeFlags.Null ? includes & TypeFlags.IncludesNonWideningType ? nullType : nullWideningType : includes & TypeFlags.Undefined ? includes & TypeFlags.IncludesNonWideningType ? undefinedType : undefinedWideningType : neverType; } } if (!origin && includes & TypeFlags.Union) { const namedUnions: Type[] = []; addNamedUnions(namedUnions, types); const reducedTypes: Type[] = []; for (const t of typeSet) { if (!some(namedUnions, union => containsType((union as UnionType).types, t))) { reducedTypes.push(t); } } if (!aliasSymbol && namedUnions.length === 1 && reducedTypes.length === 0) { return namedUnions[0]; } // We create a denormalized origin type only when the union was created from one or more named unions // (unions with alias symbols or origins) and when there is no overlap between those named unions. const namedTypesCount = reduceLeft(namedUnions, (sum, union) => sum + (union as UnionType).types.length, 0); if (namedTypesCount + reducedTypes.length === typeSet.length) { for (const t of namedUnions) { insertType(reducedTypes, t); } origin = createOriginUnionOrIntersectionType(TypeFlags.Union, reducedTypes); } } const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) | (includes & TypeFlags.Intersection ? ObjectFlags.ContainsIntersections : 0); return getUnionTypeFromSortedList(typeSet, objectFlags, aliasSymbol, aliasTypeArguments, origin); } function getUnionOrIntersectionTypePredicate(signatures: readonly Signature[], kind: TypeFlags | undefined): TypePredicate | undefined { let last: TypePredicate | undefined; const types: Type[] = []; for (const sig of signatures) { const pred = getTypePredicateOfSignature(sig); if (pred) { // Constituent type predicates must all have matching kinds. We don't create composite type predicates for assertions. if (pred.kind !== TypePredicateKind.This && pred.kind !== TypePredicateKind.Identifier || last && !typePredicateKindsMatch(last, pred)) { return undefined; } last = pred; types.push(pred.type); } else { // In composite union signatures we permit and ignore signatures with a return type `false`. const returnType = kind !== TypeFlags.Intersection ? getReturnTypeOfSignature(sig) : undefined; if (returnType !== falseType && returnType !== regularFalseType) { return undefined; } } } if (!last) { return undefined; } const compositeType = getUnionOrIntersectionType(types, kind); return createTypePredicate(last.kind, last.parameterName, last.parameterIndex, compositeType); } function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean { return a.kind === b.kind && a.parameterIndex === b.parameterIndex; } // This function assumes the constituent type list is sorted and deduplicated. function getUnionTypeFromSortedList(types: Type[], precomputedObjectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { if (types.length === 0) { return neverType; } if (types.length === 1) { return types[0]; } const typeKey = !origin ? getTypeListId(types) : origin.flags & TypeFlags.Union ? `|${getTypeListId((origin as UnionType).types)}` : origin.flags & TypeFlags.Intersection ? `&${getTypeListId((origin as IntersectionType).types)}` : `#${(origin as IndexType).type.id}|${getTypeListId(types)}`; // origin type id alone is insufficient, as `keyof x` may resolve to multiple WIP values while `x` is still resolving const id = typeKey + getAliasId(aliasSymbol, aliasTypeArguments); let type = unionTypes.get(id); if (!type) { type = createType(TypeFlags.Union) as UnionType; type.objectFlags = precomputedObjectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); type.types = types; type.origin = origin; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; if (types.length === 2 && types[0].flags & TypeFlags.BooleanLiteral && types[1].flags & TypeFlags.BooleanLiteral) { type.flags |= TypeFlags.Boolean; (type as UnionType & IntrinsicType).intrinsicName = "boolean"; } unionTypes.set(id, type); } return type; } function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const aliasSymbol = getAliasSymbolForTypeNode(node); links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); } return links.resolvedType; } function addTypeToIntersection(typeSet: Map, includes: TypeFlags, type: Type) { const flags = type.flags; if (flags & TypeFlags.Intersection) { return addTypesToIntersection(typeSet, includes, (type as IntersectionType).types); } if (isEmptyAnonymousObjectType(type)) { if (!(includes & TypeFlags.IncludesEmptyObject)) { includes |= TypeFlags.IncludesEmptyObject; typeSet.set(type.id.toString(), type); } } else { if (flags & TypeFlags.AnyOrUnknown) { if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; if (isErrorType(type)) includes |= TypeFlags.IncludesError; } else if (strictNullChecks || !(flags & TypeFlags.Nullable)) { if (type === missingType) { includes |= TypeFlags.IncludesMissingType; type = undefinedType; } if (!typeSet.has(type.id.toString())) { if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) { // We have seen two distinct unit types which means we should reduce to an // empty intersection. Adding TypeFlags.NonPrimitive causes that to happen. includes |= TypeFlags.NonPrimitive; } typeSet.set(type.id.toString(), type); } } includes |= flags & TypeFlags.IncludesMask; } return includes; } // Add the given types to the given type set. Order is preserved, freshness is removed from literal // types, duplicates are removed, and nested types of the given kind are flattened into the set. function addTypesToIntersection(typeSet: Map, includes: TypeFlags, types: readonly Type[]) { for (const type of types) { includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); } return includes; } function removeRedundantSupertypes(types: Type[], includes: TypeFlags) { let i = types.length; while (i > 0) { i--; const t = types[i]; const remove = t.flags & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol || t.flags & TypeFlags.Void && includes & TypeFlags.Undefined || isEmptyAnonymousObjectType(t) && includes & TypeFlags.DefinitelyNonNullable; if (remove) { orderedRemoveItemAt(types, i); } } } // Check that the given type has a match in every union. A given type is matched by // an identical type, and a literal type is additionally matched by its corresponding // primitive type, and missingType is matched by undefinedType (and vice versa). function eachUnionContains(unionTypes: UnionType[], type: Type) { for (const u of unionTypes) { if (!containsType(u.types, type)) { if (type === missingType) { return containsType(u.types, undefinedType); } if (type === undefinedType) { return containsType(u.types, missingType); } const primitive = type.flags & TypeFlags.StringLiteral ? stringType : type.flags & (TypeFlags.Enum | TypeFlags.NumberLiteral) ? numberType : type.flags & TypeFlags.BigIntLiteral ? bigintType : type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : undefined; if (!primitive || !containsType(u.types, primitive)) { return false; } } } return true; } /** * Returns true if the intersection of the template literals and string literals is the empty set, * for example `get${string}` & "setX", and should reduce to never. */ function extractRedundantTemplateLiterals(types: Type[]): boolean { let i = types.length; const literals = filter(types, t => !!(t.flags & TypeFlags.StringLiteral)); while (i > 0) { i--; const t = types[i]; if (!(t.flags & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping))) continue; for (const t2 of literals) { if (isTypeSubtypeOf(t2, t)) { // For example, `get${T}` & "getX" is just "getX", and Lowercase & "foo" is just "foo" orderedRemoveItemAt(types, i); break; } else if (isPatternLiteralType(t)) { return true; } } } return false; } function removeFromEach(types: Type[], flag: TypeFlags) { for (let i = 0; i !(t.flags & flag)); } } // If the given list of types contains more than one union of primitive types, replace the // first with a union containing an intersection of those primitive types, then remove the // other unions and return true. Otherwise, do nothing and return false. function intersectUnionsOfPrimitiveTypes(types: Type[]) { let unionTypes: UnionType[] | undefined; const index = findIndex(types, t => !!(getObjectFlags(t) & ObjectFlags.PrimitiveUnion)); if (index = T & { next: List }" cannot be reduced during its declaration. // Also, unlike union types, the order of the constituent types is preserved in order that overload resolution // for intersections of types with signatures can be deterministic. function getIntersectionType(types: readonly Type[], flags = IntersectionFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { const typeMembershipMap = new Map(); const includes = addTypesToIntersection(typeMembershipMap, 0 as TypeFlags, types); const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); let objectFlags = ObjectFlags.None; // An intersection type is considered empty if it contains // the type never, or // more than one unit type or, // an object type and a nullable type (null or undefined), or // a string-like type and a type known to be non-string-like, or // a number-like type and a type known to be non-number-like, or // a symbol-like type and a type known to be non-symbol-like, or // a void-like type and a type known to be non-void-like, or // a non-primitive type and a type known to be primitive. if (includes & TypeFlags.Never) { return contains(typeSet, silentNeverType) ? silentNeverType : neverType; } if ( strictNullChecks && includes & TypeFlags.Nullable && includes & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.IncludesEmptyObject) || includes & TypeFlags.NonPrimitive && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) || includes & TypeFlags.StringLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) || includes & TypeFlags.NumberLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) || includes & TypeFlags.BigIntLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) || includes & TypeFlags.ESSymbolLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) || includes & TypeFlags.VoidLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike) ) { return neverType; } if (includes & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) { return neverType; } if (includes & TypeFlags.Any) { return includes & TypeFlags.IncludesWildcard ? wildcardType : includes & TypeFlags.IncludesError ? errorType : anyType; } if (!strictNullChecks && includes & TypeFlags.Nullable) { return includes & TypeFlags.IncludesEmptyObject ? neverType : includes & TypeFlags.Undefined ? undefinedType : nullType; } if ( includes & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol || includes & TypeFlags.Void && includes & TypeFlags.Undefined || includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.DefinitelyNonNullable ) { if (!(flags & IntersectionFlags.NoSupertypeReduction)) removeRedundantSupertypes(typeSet, includes); } if (includes & TypeFlags.IncludesMissingType) { typeSet[typeSet.indexOf(undefinedType)] = missingType; } if (typeSet.length === 0) { return unknownType; } if (typeSet.length === 1) { return typeSet[0]; } if (typeSet.length === 2 && !(flags & IntersectionFlags.NoConstraintReduction)) { const typeVarIndex = typeSet[0].flags & TypeFlags.TypeVariable ? 0 : 1; const typeVariable = typeSet[typeVarIndex]; const primitiveType = typeSet[1 - typeVarIndex]; if ( typeVariable.flags & TypeFlags.TypeVariable && (primitiveType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) && !isGenericStringLikeType(primitiveType) || includes & TypeFlags.IncludesEmptyObject) ) { // We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}. const constraint = getBaseConstraintOfType(typeVariable); // Check that T's constraint is similarly composed of primitive types, the object type, or {}. if (constraint && everyType(constraint, t => !!(t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) || isEmptyAnonymousObjectType(t))) { // If T's constraint is a subtype of P, simply return T. For example, given `T extends "a" | "b"`, // the intersection `T & string` reduces to just T. if (isTypeStrictSubtypeOf(constraint, primitiveType)) { return typeVariable; } if (!(constraint.flags & TypeFlags.Union && someType(constraint, c => isTypeStrictSubtypeOf(c, primitiveType)))) { // No constituent of T's constraint is a subtype of P. If P is also not a subtype of T's constraint, // then the constraint and P are unrelated, and the intersection reduces to never. For example, given // `T extends "a" | "b"`, the intersection `T & number` reduces to never. if (!isTypeStrictSubtypeOf(primitiveType, constraint)) { return neverType; } } // Some constituent of T's constraint is a subtype of P, or P is a subtype of T's constraint. Thus, // the intersection further constrains the type variable. For example, given `T extends string | number`, // the intersection `T & "a"` is marked as a constrained type variable. Likewise, given `T extends "a" | 1`, // the intersection `T & number` is marked as a constrained type variable. objectFlags = ObjectFlags.IsConstrainedTypeVariable; } } } const id = getTypeListId(typeSet) + (flags & IntersectionFlags.NoConstraintReduction ? "*" : getAliasId(aliasSymbol, aliasTypeArguments)); let result = intersectionTypes.get(id); if (!result) { if (includes & TypeFlags.Union) { if (intersectUnionsOfPrimitiveTypes(typeSet)) { // When the intersection creates a reduced set (which might mean that *all* union types have // disappeared), we restart the operation to get a new set of combined flags. Once we have // reduced we'll never reduce again, so this occurs at most once. result = getIntersectionType(typeSet, flags, aliasSymbol, aliasTypeArguments); } else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && (t as UnionType).types[0].flags & TypeFlags.Undefined))) { const containedUndefinedType = some(typeSet, containsMissingType) ? missingType : undefinedType; removeFromEach(typeSet, TypeFlags.Undefined); result = getUnionType([getIntersectionType(typeSet, flags), containedUndefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && ((t as UnionType).types[0].flags & TypeFlags.Null || (t as UnionType).types[1].flags & TypeFlags.Null)))) { removeFromEach(typeSet, TypeFlags.Null); result = getUnionType([getIntersectionType(typeSet, flags), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } else if (typeSet.length >= 3 && types.length > 2) { // When we have three or more constituents, more than two inputs (to head off infinite reexpansion), some of which are unions, we employ a "divide and conquer" strategy // where A & B & C & D is processed as (A & B) & (C & D). Since intersections of unions often produce far smaller // unions of intersections than the full cartesian product (due to some intersections becoming `never`), this can // dramatically reduce the overall work. const middle = Math.floor(typeSet.length / 2); result = getIntersectionType([getIntersectionType(typeSet.slice(0, middle), flags), getIntersectionType(typeSet.slice(middle), flags)], flags, aliasSymbol, aliasTypeArguments); } else { // We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of // the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type // exceeds 100000 constituents, report an error. if (!checkCrossProductUnion(typeSet)) { return errorType; } const constituents = getCrossProductIntersections(typeSet, flags); // We attach a denormalized origin type when at least one constituent of the cross-product union is an // intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions) and // the denormalized origin has fewer constituents than the union itself. const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) && getConstituentCountOfTypes(constituents) > getConstituentCountOfTypes(typeSet) ? createOriginUnionOrIntersectionType(TypeFlags.Intersection, typeSet) : undefined; result = getUnionType(constituents, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin); } } else { result = createIntersectionType(typeSet, objectFlags, aliasSymbol, aliasTypeArguments); } intersectionTypes.set(id, result); } return result; } function getCrossProductUnionSize(types: readonly Type[]) { return reduceLeft(types, (n, t) => t.flags & TypeFlags.Union ? n * (t as UnionType).types.length : t.flags & TypeFlags.Never ? 0 : n, 1); } function checkCrossProductUnion(types: readonly Type[]) { const size = getCrossProductUnionSize(types); if (size >= 100000) { tracing?.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_DepthLimit", { typeIds: types.map(t => t.id), size }); error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); return false; } return true; } function getCrossProductIntersections(types: readonly Type[], flags: IntersectionFlags) { const count = getCrossProductUnionSize(types); const intersections: Type[] = []; for (let i = 0; i = 0; j--) { if (types[j].flags & TypeFlags.Union) { const sourceTypes = (types[j] as UnionType).types; const length = sourceTypes.length; constituents[j] = sourceTypes[n % length]; n = Math.floor(n / length); } } const t = getIntersectionType(constituents, flags); if (!(t.flags & TypeFlags.Never)) intersections.push(t); } return intersections; } function getConstituentCount(type: Type): number { return !(type.flags & TypeFlags.UnionOrIntersection) || type.aliasSymbol ? 1 : type.flags & TypeFlags.Union && (type as UnionType).origin ? getConstituentCount((type as UnionType).origin!) : getConstituentCountOfTypes((type as UnionOrIntersectionType).types); } function getConstituentCountOfTypes(types: Type[]): number { return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0); } function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const aliasSymbol = getAliasSymbolForTypeNode(node); const types = map(node.types, getTypeFromTypeNode); // We perform no supertype reduction for X & {} or {} & X, where X is one of string, number, bigint, // or a pattern literal template type. This enables union types like "a" | "b" | string & {} or // "aa" | "ab" | `a${string}` which preserve the literal types for purposes of statement completion. const emptyIndex = types.length === 2 ? types.indexOf(emptyTypeLiteralType) : -1; const t = emptyIndex >= 0 ? types[1 - emptyIndex] : unknownType; const noSupertypeReduction = !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt) || t.flags & TypeFlags.TemplateLiteral && isPatternLiteralType(t)); links.resolvedType = getIntersectionType(types, noSupertypeReduction ? IntersectionFlags.NoSupertypeReduction : 0, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); } return links.resolvedType; } function createIndexType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { const result = createType(TypeFlags.Index) as IndexType; result.type = type; result.indexFlags = indexFlags; return result; } function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) { const result = createOriginType(TypeFlags.Index) as IndexType; result.type = type; return result; } function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { return indexFlags & IndexFlags.StringsOnly ? type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, IndexFlags.StringsOnly)) : type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, IndexFlags.None)); } /** * This roughly mirrors `resolveMappedTypeMembers` in the nongeneric case, except only reports a union of the keys calculated, * rather than manufacturing the properties. We can't just fetch the `constraintType` since that would ignore mappings * and mapping the `constraintType` directly ignores how mapped types map _properties_ and not keys (thus ignoring subtype * reduction in the constraintType) when possible. * @param noIndexSignatures Indicates if _string_ index signatures should be elided. (other index signatures are always reported) */ function getIndexTypeForMappedType(type: MappedType, indexFlags: IndexFlags) { const typeParameter = getTypeParameterFromMappedType(type); const constraintType = getConstraintTypeFromMappedType(type); const nameType = getNameTypeFromMappedType(type.target as MappedType || type); if (!nameType && !(indexFlags & IndexFlags.NoIndexSignatures)) { // no mapping and no filtering required, just quickly bail to returning the constraint in the common case return constraintType; } const keyTypes: Type[] = []; // Calling getApparentType on the `T` of a `keyof T` in the constraint type of a generic mapped type can // trigger a circularity. For example, `T extends { [P in keyof T & string as Captitalize

]: any }` is // a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic. if (isGenericIndexType(constraintType)) { if (isMappedTypeWithKeyofConstraintDeclaration(type)) { // We have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer // the whole `keyof whatever` for later since it's not safe to resolve the shape of modifier type. return getIndexTypeForGenericType(type, indexFlags); } // Include the generic component in the resulting type. forEachType(constraintType, addMemberForKeyType); } else if (isMappedTypeWithKeyofConstraintDeclaration(type)) { const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, !!(indexFlags & IndexFlags.StringsOnly), addMemberForKeyType); } else { forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); } // We had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the // original constraintType, so we can return the union that preserves aliases/origin data if possible. const result = indexFlags & IndexFlags.NoIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes); if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)) { return constraintType; } return result; function addMemberForKeyType(keyType: Type) { const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; // `keyof` currently always returns `string | number` for concrete `string` index signatures - the below ternary keeps that behavior for mapped types // See `getLiteralTypeFromProperties` where there's a similar ternary to cause the same behavior. keyTypes.push(propNameType === stringType ? stringOrNumberType : propNameType); } } // Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N

]: X }, to simply N. This however presumes // that N distributes over union types, i.e. that N is equivalent to N | N | N. Specifically, we only // want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable // introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because // they're the same type regardless of what's being distributed over. function hasDistributiveNameType(mappedType: MappedType) { const typeVariable = getTypeParameterFromMappedType(mappedType); return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable); function isDistributive(type: Type): boolean { return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true : type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable : type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) : type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) : type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).baseType) && isDistributive((type as SubstitutionType).constraint) : type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) : false; } } function getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) { if (isPrivateIdentifier(name)) { return neverType; } if (isNumericLiteral(name)) { return getRegularTypeOfLiteralType(checkExpression(name)); } if (isComputedPropertyName(name)) { return getRegularTypeOfLiteralType(checkComputedPropertyName(name)); } const propertyName = getPropertyNameForPropertyNameNode(name); if (propertyName !== undefined) { return getStringLiteralType(unescapeLeadingUnderscores(propertyName)); } if (isExpression(name)) { return getRegularTypeOfLiteralType(checkExpression(name)); } return neverType; } function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags, includeNonPublic?: boolean) { if (includeNonPublic || !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) { let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType; if (!type) { const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName | JsxAttributeName; type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") : name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined); } if (type && type.flags & include) { return type; } } return neverType; } function isKeyTypeIncluded(keyType: Type, include: TypeFlags): boolean { return !!(keyType.flags & include || keyType.flags & TypeFlags.Intersection && some((keyType as IntersectionType).types, t => isKeyTypeIncluded(t, include))); } function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) { const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createOriginIndexType(type) : undefined; const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include)); const indexKeyTypes = map(getIndexInfosOfType(type), info => info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ? info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType); return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin); } function shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) { return !!(type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) || type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) || type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); } function getIndexType(type: Type, indexFlags = IndexFlags.None): Type { type = getReducedType(type); return isNoInferType(type) ? getNoInferType(getIndexType((type as SubstitutionType).baseType, indexFlags)) : shouldDeferIndexType(type, indexFlags) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, indexFlags) : type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, indexFlags))) : type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, indexFlags))) : getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, indexFlags) : type === wildcardType ? wildcardType : type.flags & TypeFlags.Unknown ? neverType : type.flags & (TypeFlags.Any | TypeFlags.Never) ? stringNumberSymbolType : getLiteralTypeFromProperties(type, (indexFlags & IndexFlags.NoIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (indexFlags & IndexFlags.StringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), indexFlags === IndexFlags.None); } function getExtractStringType(type: Type) { const extractTypeAlias = getGlobalExtractSymbol(); return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; } function getIndexTypeOrString(type: Type): Type { const indexType = getExtractStringType(getIndexType(type)); return indexType.flags & TypeFlags.Never ? stringType : indexType; } function getTypeFromTypeOperatorNode(node: TypeOperatorNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { switch (node.operator) { case SyntaxKind.KeyOfKeyword: links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); break; case SyntaxKind.UniqueKeyword: links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword ? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent)) : errorType; break; case SyntaxKind.ReadonlyKeyword: links.resolvedType = getTypeFromTypeNode(node.type); break; default: Debug.assertNever(node.operator); } } return links.resolvedType; } function getTypeFromTemplateTypeNode(node: TemplateLiteralTypeNode) { const links = getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = getTemplateLiteralType( [node.head.text, ...map(node.templateSpans, span => span.literal.text)], map(node.templateSpans, span => getTypeFromTypeNode(span.type)), ); } return links.resolvedType; } function getTemplateLiteralType(texts: readonly string[], types: readonly Type[]): Type { const unionIndex = findIndex(types, t => !!(t.flags & (TypeFlags.Never | TypeFlags.Union))); if (unionIndex >= 0) { return checkCrossProductUnion(types) ? mapType(types[unionIndex], t => getTemplateLiteralType(texts, replaceElement(types, unionIndex, t))) : errorType; } if (contains(types, wildcardType)) { return wildcardType; } const newTypes: Type[] = []; const newTexts: string[] = []; let text = texts[0]; if (!addSpans(texts, types)) { return stringType; } if (newTypes.length === 0) { return getStringLiteralType(text); } newTexts.push(text); if (every(newTexts, t => t === "")) { if (every(newTypes, t => !!(t.flags & TypeFlags.String))) { return stringType; } // Normalize `${Mapping}` into Mapping if (newTypes.length === 1 && isPatternLiteralType(newTypes[0])) { return newTypes[0]; } } const id = `${getTypeListId(newTypes)}|${map(newTexts, t => t.length).join(",")}|${newTexts.join("")}`; let type = templateLiteralTypes.get(id); if (!type) { templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes)); } return type; function addSpans(texts: readonly string[], types: readonly Type[]): boolean { for (let i = 0; i getStringMappingType(symbol, t)) : type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) : type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) : // Mapping> === Mapping type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type : type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.StringMapping) || isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) : // This handles Mapping and Mapping isPatternLiteralPlaceholderType(type) ? getStringMappingTypeForGenericType(symbol, getTemplateLiteralType(["", ""], [type])) : type; } function applyStringMapping(symbol: Symbol, str: string) { switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { case IntrinsicTypeKind.Uppercase: return str.toUpperCase(); case IntrinsicTypeKind.Lowercase: return str.toLowerCase(); case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1); case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1); } return str; } function applyTemplateStringMapping(symbol: Symbol, texts: readonly string[], types: readonly Type[]): [texts: readonly string[], types: readonly Type[]] { switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { case IntrinsicTypeKind.Uppercase: return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))]; case IntrinsicTypeKind.Lowercase: return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))]; case IntrinsicTypeKind.Capitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types]; case IntrinsicTypeKind.Uncapitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toLowerCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types]; } return [texts, types]; } function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type { const id = `${getSymbolId(symbol)},${getTypeId(type)}`; let result = stringMappingTypes.get(id); if (!result) { stringMappingTypes.set(id, result = createStringMappingType(symbol, type)); } return result; } function createStringMappingType(symbol: Symbol, type: Type) { const result = createTypeWithSymbol(TypeFlags.StringMapping, symbol) as StringMappingType; result.type = type; return result; } function createIndexedAccessType(objectType: Type, indexType: Type, accessFlags: AccessFlags, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { const type = createType(TypeFlags.IndexedAccess) as IndexedAccessType; type.objectType = objectType; type.indexType = indexType; type.accessFlags = accessFlags; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; return type; } /** * Returns if a type is or consists of a JSLiteral object type * In addition to objects which are directly literals, * * unions where every element is a jsliteral * * intersections where at least one element is a jsliteral * * and instantiable types constrained to a jsliteral * Should all count as literals and not print errors on access or assignment of possibly existing properties. * This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference). */ function isJSLiteralType(type: Type): boolean { if (noImplicitAny) { return false; // Flag is meaningless under `noImplicitAny` mode } if (getObjectFlags(type) & ObjectFlags.JSLiteral) { return true; } if (type.flags & TypeFlags.Union) { return every((type as UnionType).types, isJSLiteralType); } if (type.flags & TypeFlags.Intersection) { return some((type as IntersectionType).types, isJSLiteralType); } if (type.flags & TypeFlags.Instantiable) { const constraint = getResolvedBaseConstraint(type); return constraint !== type && isJSLiteralType(constraint); } return false; } function getPropertyNameFromIndex(indexType: Type, accessNode: PropertyName | ObjectBindingPattern | ArrayBindingPattern | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) { return isTypeUsableAsPropertyName(indexType) ? getPropertyNameFromType(indexType) : accessNode && isPropertyName(accessNode) ? // late bound names are handled in the first branch, so here we only need to handle normal names getPropertyNameForPropertyNameNode(accessNode) : undefined; } function isUncalledFunctionReference(node: Node, symbol: Symbol) { if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { const parent = findAncestor(node.parent, n => !isAccessExpression(n)) || node.parent; if (isCallLikeExpression(parent)) { return isCallOrNewExpression(parent) && isIdentifier(node) && hasMatchingArgument(parent, node); } return every(symbol.declarations, d => !isFunctionLike(d) || isDeprecatedDeclaration(d)); } return true; } function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode); if (propName !== undefined) { if (accessFlags & AccessFlags.Contextual) { return getTypeOfPropertyOfContextualType(objectType, propName) || anyType; } const prop = getPropertyOfType(objectType, propName); if (prop) { if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && isDeprecatedSymbol(prop) && isUncalledFunctionReference(accessNode, prop)) { const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string); } if (accessExpression) { markPropertyAsReferenced(prop, accessExpression, isSelfTypeAccess(accessExpression.expression, objectType.symbol)); if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) { error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); return undefined; } if (accessFlags & AccessFlags.CacheSymbol) { getNodeLinks(accessNode!).resolvedSymbol = prop; } if (isThisPropertyAccessInConstructor(accessExpression, prop)) { return autoType; } } const propType = accessFlags & AccessFlags.Writing ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop); return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? getFlowTypeOfReference(accessExpression, propType) : accessNode && isIndexedAccessTypeNode(accessNode) && containsMissingType(propType) ? getUnionType([propType, undefinedType]) : propType; } if (everyType(objectType, isTupleType) && isNumericLiteralName(propName)) { const index = +propName; if (accessNode && everyType(objectType, t => !((t as TupleTypeReference).target.combinedFlags & ElementFlags.Variable)) && !(accessFlags & AccessFlags.AllowMissing)) { const indexNode = getIndexNodeForAccessExpression(accessNode); if (isTupleType(objectType)) { if (index = 0) { errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType)); return getTupleElementTypeOutOfStartCount(objectType, index, accessFlags & AccessFlags.IncludeUndefined ? missingType : undefined); } } } if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) { return objectType; } // If no index signature is applicable, we default to the string index signature. In effect, this means the string // index signature applies even when accessing with a symbol-like type. const indexInfo = getApplicableIndexInfo(objectType, indexType) || getIndexInfoOfType(objectType, stringType); if (indexInfo) { if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo.keyType !== numberType) { if (accessExpression) { if (accessFlags & AccessFlags.Writing) { error(accessExpression, Diagnostics.Type_0_is_generic_and_can_only_be_indexed_for_reading, typeToString(originalObjectType)); } else { error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); } } return undefined; } if (accessNode && indexInfo.keyType === stringType && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { const indexNode = getIndexNodeForAccessExpression(accessNode); error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, missingType]) : indexInfo.type; } errorIfWritingToReadonlyIndex(indexInfo); // When accessing an enum object with its own type, // e.g. E[E.A] for enum E { A }, undefined shouldn't // be included in the result type if ( (accessFlags & AccessFlags.IncludeUndefined) && !(objectType.symbol && objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum) && (indexType.symbol && indexType.flags & TypeFlags.EnumLiteral && getParentOfSymbol(indexType.symbol) === objectType.symbol)) ) { return getUnionType([indexInfo.type, missingType]); } return indexInfo.type; } if (indexType.flags & TypeFlags.Never) { return neverType; } if (isJSLiteralType(objectType)) { return anyType; } if (accessExpression && !isConstEnumObjectType(objectType)) { if (isObjectLiteralType(objectType)) { if (noImplicitAny && indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { diagnostics.add(createDiagnosticForNode(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType))); return undefinedType; } else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { const types = map((objectType as ResolvedType).properties, property => { return getTypeOfSymbol(property); }); return getUnionType(append(types, undefinedType)); } } if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } else if (noImplicitAny && !(accessFlags & AccessFlags.SuppressNoImplicitAnyError)) { if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { const typeName = typeToString(objectType); error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName as string, typeName, typeName + "[" + getTextOfNode(accessExpression.argumentExpression) + "]"); } else if (getIndexTypeOfType(objectType, numberType)) { error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); } else { let suggestion: string | undefined; if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) { if (suggestion !== undefined) { error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion); } } else { const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType); if (suggestion !== undefined) { error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion); } else { let errorInfo: DiagnosticMessageChain | undefined; if (indexType.flags & TypeFlags.EnumLiteral) { errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType)); } else if (indexType.flags & TypeFlags.UniqueESSymbol) { const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression); errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType)); } else if (indexType.flags & TypeFlags.StringLiteral) { errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType)); } else if (indexType.flags & TypeFlags.NumberLiteral) { errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType)); } else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType)); } errorInfo = chainDiagnosticMessages( errorInfo, Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType), ); diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(accessExpression), accessExpression, errorInfo)); } } } } return undefined; } } if (accessFlags & AccessFlags.AllowMissing && isObjectLiteralType(objectType)) { return undefinedType; } if (isJSLiteralType(objectType)) { return anyType; } if (accessNode) { const indexNode = getIndexNodeForAccessExpression(accessNode); if (indexNode.kind !== SyntaxKind.BigIntLiteral && indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType as StringLiteralType | NumberLiteralType).value, typeToString(objectType)); } else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) { error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType)); } else { const typeString = indexNode.kind === SyntaxKind.BigIntLiteral ? "bigint" : typeToString(indexType); error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeString); } } if (isTypeAny(indexType)) { return indexType; } return undefined; function errorIfWritingToReadonlyIndex(indexInfo: IndexInfo | undefined): void { if (indexInfo && indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } } } function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType : accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression : accessNode; } function isPatternLiteralPlaceholderType(type: Type): boolean { if (type.flags & TypeFlags.Intersection) { // Return true if the intersection consists of one or more placeholders and zero or // more object type tags. let seenPlaceholder = false; for (const t of (type as IntersectionType).types) { if (t.flags & (TypeFlags.Literal | TypeFlags.Nullable) || isPatternLiteralPlaceholderType(t)) { seenPlaceholder = true; } else if (!(t.flags & TypeFlags.Object)) { return false; } } return seenPlaceholder; } return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type); } function isPatternLiteralType(type: Type) { // A pattern literal type is a template literal or a string mapping type that contains only // non-generic pattern literal placeholders. return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) || !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); } function isGenericStringLikeType(type: Type) { return !!(type.flags & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) && !isPatternLiteralType(type); } function isGenericType(type: Type): boolean { return !!getGenericObjectFlags(type); } function isGenericObjectType(type: Type): boolean { return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType); } function isGenericIndexType(type: Type): boolean { return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericIndexType); } function getGenericObjectFlags(type: Type): ObjectFlags { if (type.flags & (TypeFlags.UnionOrIntersection)) { if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { (type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0); } return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType; } if (type.flags & TypeFlags.Substitution) { if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { (type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | getGenericObjectFlags((type as SubstitutionType).baseType) | getGenericObjectFlags((type as SubstitutionType).constraint); } return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType; } return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index) || isGenericStringLikeType(type) ? ObjectFlags.IsGenericIndexType : 0); } function getSimplifiedType(type: Type, writing: boolean): Type { return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) : type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) : type; } function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) { // (T | U)[K] -> T[K] | U[K] (reading) // (T | U)[K] -> T[K] & U[K] (writing) // (T & U)[K] -> T[K] & U[K] if (objectType.flags & TypeFlags.Union || objectType.flags & TypeFlags.Intersection && !shouldDeferIndexType(objectType)) { const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing)); return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types); } } function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) { // T[A | B] -> T[A] | T[B] (reading) // T[A | B] -> T[A] & T[B] (writing) if (indexType.flags & TypeFlags.Union) { const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing)); return writing ? getIntersectionType(types) : getUnionType(types); } } // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return // the type itself if no transformation is possible. The writing flag indicates that the type is // the target of an assignment. function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): Type { const cache = writing ? "simplifiedForWriting" : "simplifiedForReading"; if (type[cache]) { return type[cache] === circularConstraintType ? type : type[cache]; } type[cache] = circularConstraintType; // We recursively simplify the object type as it may in turn be an indexed access type. For example, with // '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type. const objectType = getSimplifiedType(type.objectType, writing); const indexType = getSimplifiedType(type.indexType, writing); // T[A | B] -> T[A] | T[B] (reading) // T[A | B] -> T[A] & T[B] (writing) const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing); if (distributedOverIndex) { return type[cache] = distributedOverIndex; } // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again if (!(indexType.flags & TypeFlags.Instantiable)) { // (T | U)[K] -> T[K] | U[K] (reading) // (T | U)[K] -> T[K] & U[K] (writing) // (T & U)[K] -> T[K] & U[K] const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing); if (distributedOverObject) { return type[cache] = distributedOverObject; } } // So ultimately (reading): // ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2] // A generic tuple type indexed by a number exists only when the index type doesn't select a // fixed element. We simplify to either the combined type of all elements (when the index type // the actual number type) or to the combined type of all non-fixed elements. if (isGenericTupleType(objectType) && indexType.flags & TypeFlags.NumberLike) { const elementType = getElementTypeOfSliceOfTupleType(objectType, indexType.flags & TypeFlags.Number ? 0 : objectType.target.fixedLength, /*endSkipCount*/ 0, writing); if (elementType) { return type[cache] = elementType; } } // If the object type is a mapped type { [P in K]: E }, where K is generic, or { [P in K as N]: E }, where // K is generic and N is assignable to P, instantiate E using a mapper that substitutes the index type for P. // For example, for an index access { [P in K]: Box }[X], we construct the type Box. if (isGenericMappedType(objectType)) { if (getMappedTypeNameTypeKind(objectType) !== MappedTypeNameTypeKind.Remapping) { return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); } } return type[cache] = type; } function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { const checkType = type.checkType; const extendsType = type.extendsType; const trueType = getTrueTypeFromConditionalType(type); const falseType = getFalseTypeFromConditionalType(type); // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true return getSimplifiedType(trueType, writing); } else if (isIntersectionEmpty(checkType, extendsType)) { // Always false return neverType; } } else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true return neverType; } else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false return getSimplifiedType(falseType, writing); } } return type; } /** * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent */ function isIntersectionEmpty(type1: Type, type2: Type) { return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); } // Given an indexed access on a mapped type of the form { [P in K]: E }[X], return an instantiation of E where P is // replaced with X. Since this simplification doesn't account for mapped type modifiers, add 'undefined' to the // resulting type if the mapped type includes a '?' modifier or if the modifiers type indicates that some properties // are optional. If the modifiers type is generic, conservatively estimate optionality by recursively looking for // mapped types that include '?' modifiers. function substituteIndexedMappedType(objectType: MappedType, index: Type) { const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); const templateMapper = combineTypeMappers(objectType.mapper, mapper); const instantiatedTemplateType = instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); const isOptional = getMappedTypeOptionality(objectType) > 0 || (isGenericType(objectType) ? getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(objectType)) > 0 : couldAccessOptionalProperty(objectType, index)); return addOptionality(instantiatedTemplateType, /*isProperty*/ true, isOptional); } // Return true if an indexed access with the given object and index types could access an optional property. function couldAccessOptionalProperty(objectType: Type, indexType: Type) { const indexConstraint = getBaseConstraintOfType(indexType); return !!indexConstraint && some(getPropertiesOfType(objectType), p => !!(p.flags & SymbolFlags.Optional) && isTypeAssignableTo(getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique), indexConstraint)); } function getIndexedAccessType(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { return getIndexedAccessTypeOrUndefined(objectType, indexType, accessFlags, accessNode, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType); } function indexTypeLessThan(indexType: Type, limit: number) { return everyType(indexType, t => { if (t.flags & TypeFlags.StringOrNumberLiteral) { const propName = getPropertyNameFromType(t as StringLiteralType | NumberLiteralType); if (isNumericLiteralName(propName)) { const index = +propName; return index >= 0 && index 0 && !some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken)); } function isDeferredType(type: Type, checkTuples: boolean) { return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType); } function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { let result; let extraTypes: Type[] | undefined; let tailCount = 0; // We loop here for an immediately nested conditional type in the false position, effectively treating // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive // cases we increment the tail recursion counter and stop after 1000 iterations. while (true) { if (tailCount === 1000) { error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } const checkType = instantiateType(getActualTypeVariable(root.checkType), mapper); const extendsType = instantiateType(root.extendsType, mapper); if (checkType === errorType || extendsType === errorType) { return errorType; } if (checkType === wildcardType || extendsType === wildcardType) { return wildcardType; } const checkTypeNode = skipTypeParentheses(root.node.checkType); const extendsTypeNode = skipTypeParentheses(root.node.extendsType); // When the check and extends types are simple tuple types of the same arity, we defer resolution of the // conditional type when any tuple elements are generic. This is such that non-distributable conditional // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) && length((checkTypeNode as TupleTypeNode).elements) === length((extendsTypeNode as TupleTypeNode).elements); const checkTypeDeferred = isDeferredType(checkType, checkTuples); let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be // instantiated with the context, so it doesn't need the mapper for the inference context - however the constraint // may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2]. // [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated // as `number` // Conversely, if we have `Foo`, `B` is still constrained to `T` and `T` is instantiated as `A` // [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T` // which is in turn instantiated as `Q`, which is in turn instantiated as `number`. // So we need to: // * combine `context.nonFixingMapper` with `mapper` so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information) // * incorporate all of the component mappers into the combined mapper for the true and false members // This means we have two mappers that need applying: // * The original `mapper` used to create this conditional // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); if (mapper) { context.nonFixingMapper = combineTypeMappers(context.nonFixingMapper, mapper); } if (!checkTypeDeferred) { // We don't want inferences from constraints as they may cause us to eagerly resolve the // conditional type instead of deferring resolution. Also, we always want strict function // types rules (i.e. proper contravariance) for inferences. inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); } // It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the // those type parameters are used in type references (see getInferredTypeParameterConstraint). For // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. combinedMapper = mapper ? combineTypeMappers(context.mapper, mapper) : context.mapper; } // Instantiate the extends type including inferences for 'infer T' type parameters const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; // We attempt to resolve the conditional type only when the check and extends types are non-generic if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { // Return falseType for a definitely false extends check. We check an instantiations of the two // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { // Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a // distributive conditional type applied to the constraint of a type variable, include trueType if // there are possible values of the check type that are also possible values of the extends type. // We use a reverse assignability check as it is less expensive than the comparable relationship // and avoids false positives of a non-empty intersection check. if (checkType.flags & TypeFlags.Any || forConstraint && !(inferredExtendsType.flags & TypeFlags.Never) && someType(getPermissiveInstantiation(inferredExtendsType), t => isTypeAssignableTo(t, getPermissiveInstantiation(checkType)))) { (extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper)); } // If falseType is an immediately nested conditional type that isn't distributive or has an // identical checkType, switch to that type and loop. const falseType = getTypeFromTypeNode(root.node.falseType); if (falseType.flags & TypeFlags.Conditional) { const newRoot = (falseType as ConditionalType).root; if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) { root = newRoot; continue; } if (canTailRecurse(falseType, mapper)) { continue; } } result = instantiateType(falseType, mapper); break; } // Return trueType for a definitely true extends check. We check instantiations of the two // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter // that has no constraint. This ensures that, for example, the type // type Foo = T extends { x: string } ? string : number // doesn't immediately resolve to 'string' instead of being deferred. if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { const trueType = getTypeFromTypeNode(root.node.trueType); const trueMapper = combinedMapper || mapper; if (canTailRecurse(trueType, trueMapper)) { continue; } result = instantiateType(trueType, trueMapper); break; } } // Return a deferred type for a check that is neither definitely true nor definitely false result = createType(TypeFlags.Conditional) as ConditionalType; result.root = root; result.checkType = instantiateType(root.checkType, mapper); result.extendsType = instantiateType(root.extendsType, mapper); result.mapper = mapper; result.combinedMapper = combinedMapper; result.aliasSymbol = aliasSymbol || root.aliasSymbol; result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 break; } return extraTypes ? getUnionType(append(extraTypes, result)) : result; // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail // recursion counter for those. function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) { if (newType.flags & TypeFlags.Conditional && newMapper) { const newRoot = (newType as ConditionalType).root; if (newRoot.outerTypeParameters) { const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { root = newRoot; mapper = newRootMapper; aliasSymbol = undefined; aliasTypeArguments = undefined; if (newRoot.aliasSymbol) { tailCount++; } return true; } } } return false; } } function getTrueTypeFromConditionalType(type: ConditionalType) { return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); } function getFalseTypeFromConditionalType(type: ConditionalType) { return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper)); } function getInferredTrueTypeFromConditionalType(type: ConditionalType) { return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type)); } function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { let result: TypeParameter[] | undefined; if (node.locals) { node.locals.forEach(symbol => { if (symbol.flags & SymbolFlags.TypeParameter) { result = append(result, getDeclaredTypeOfSymbol(symbol)); } }); } return result; } function isDistributionDependent(root: ConditionalRoot) { return root.isDistributive && ( isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.trueType) || isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.falseType) ); } function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const checkType = getTypeFromTypeNode(node.checkType); const aliasSymbol = getAliasSymbolForTypeNode(node); const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node)); const root: ConditionalRoot = { node, checkType, extendsType: getTypeFromTypeNode(node.extendsType), isDistributive: !!(checkType.flags & TypeFlags.TypeParameter), inferTypeParameters: getInferTypeParameters(node), outerTypeParameters, instantiations: undefined, aliasSymbol, aliasTypeArguments, }; links.resolvedType = getConditionalType(root, /*mapper*/ undefined, /*forConstraint*/ false); if (outerTypeParameters) { root.instantiations = new Map(); root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); } } return links.resolvedType; } function getTypeFromInferTypeNode(node: InferTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter)); } return links.resolvedType; } function getIdentifierChain(node: EntityName): Identifier[] { if (isIdentifier(node)) { return [node]; } else { return append(getIdentifierChain(node.left), node.right); } } function getTypeFromImportTypeNode(node: ImportTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { if (!isLiteralImportTypeNode(node)) { error(node.argument, Diagnostics.String_literal_expected); links.resolvedSymbol = unknownSymbol; return links.resolvedType = errorType; } const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type; // TODO: Future work: support unions/generics/whatever via a deferred import-type const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal); if (!innerModuleSymbol) { links.resolvedSymbol = unknownSymbol; return links.resolvedType = errorType; } const isExportEquals = !!innerModuleSymbol.exports?.get(InternalSymbolName.ExportEquals); const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false); if (!nodeIsMissing(node.qualifier)) { const nameStack: Identifier[] = getIdentifierChain(node.qualifier!); let currentNamespace = moduleSymbol; let current: Identifier | undefined; while (current = nameStack.shift()) { const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning; // typeof a.b.c is normally resolved using `checkExpression` which in turn defers to `checkQualifiedName` // That, in turn, ultimately uses `getPropertyOfType` on the type of the symbol, which differs slightly from // the `exports` lookup process that only looks up namespace members which is used for most type references const mergedResolvedSymbol = getMergedSymbol(resolveSymbol(currentNamespace)); const symbolFromVariable = node.isTypeOf || isInJSFile(node) && isExportEquals ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText, /*skipObjectFunctionPropertyAugment*/ false, /*includeTypeOnlyMembers*/ true) : undefined; const symbolFromModule = node.isTypeOf ? undefined : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning); const next = symbolFromModule ?? symbolFromVariable; if (!next) { error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); return links.resolvedType = errorType; } getNodeLinks(current).resolvedSymbol = next; getNodeLinks(current.parent).resolvedSymbol = next; currentNamespace = next; } links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning); } else { if (moduleSymbol.flags & targetMeaning) { links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning); } else { const errorMessage = targetMeaning === SymbolFlags.Value ? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here : Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0; error(node, errorMessage, node.argument.literal.text); links.resolvedSymbol = unknownSymbol; links.resolvedType = errorType; } } } return links.resolvedType; } function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { const resolvedSymbol = resolveSymbol(symbol); links.resolvedSymbol = resolvedSymbol; if (meaning === SymbolFlags.Value) { return getInstantiationExpressionType(getTypeOfSymbol(symbol), node); // intentionally doesn't use resolved symbol so type is cached as expected on the alias } else { return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol } } function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature): Type { const links = getNodeLinks(node); if (!links.resolvedType) { // Deferred resolution of members is handled by resolveObjectTypeMembers const aliasSymbol = getAliasSymbolForTypeNode(node); if (!node.symbol || getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) { links.resolvedType = emptyTypeLiteralType; } else { let type = createObjectType(ObjectFlags.Anonymous, node.symbol); type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); if (isJSDocTypeLiteral(node) && node.isArrayType) { type = createArrayType(type); } links.resolvedType = type; } } return links.resolvedType; } function getAliasSymbolForTypeNode(node: Node) { let host = node.parent; while (isParenthesizedTypeNode(host) || isJSDocTypeExpression(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) { host = host.parent; } return isTypeAlias(host) ? getSymbolOfDeclaration(host) : undefined; } function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) { return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; } function isNonGenericObjectType(type: Type) { return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type); } function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) { return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)); } function tryMergeUnionOfObjectTypeAndEmptyObject(type: Type, readonly: boolean): Type { if (!(type.flags & TypeFlags.Union)) { return type; } if (every((type as UnionType).types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) { return find((type as UnionType).types, isEmptyObjectType) || emptyObjectType; } const firstType = find((type as UnionType).types, t => !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); if (!firstType) { return type; } const secondType = find((type as UnionType).types, t => t !== firstType && !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); if (secondType) { return type; } return getAnonymousPartialType(firstType); function getAnonymousPartialType(type: Type) { // gets the type as if it had been spread, but where everything in the spread is made optional const members = createSymbolTable(); for (const prop of getPropertiesOfType(type)) { if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) { // do nothing, skip privates } else if (isSpreadableProperty(prop)) { const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); const flags = SymbolFlags.Property | SymbolFlags.Optional; const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); result.links.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true); result.declarations = prop.declarations; result.links.nameType = getSymbolLinks(prop).nameType; result.links.syntheticOrigin = prop; members.set(prop.escapedName, result); } } const spread = createAnonymousType(type.symbol, members, emptyArray, emptyArray, getIndexInfosOfType(type)); spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; return spread; } } /** * Since the source of spread types are object literals, which are not binary, * this function should be called in a left folding style, with left = previous result of getSpreadType * and right = the new element to be spread. */ function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): Type { if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) { return anyType; } if (left.flags & TypeFlags.Unknown || right.flags & TypeFlags.Unknown) { return unknownType; } if (left.flags & TypeFlags.Never) { return right; } if (right.flags & TypeFlags.Never) { return left; } left = tryMergeUnionOfObjectTypeAndEmptyObject(left, readonly); if (left.flags & TypeFlags.Union) { return checkCrossProductUnion([left, right]) ? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)) : errorType; } right = tryMergeUnionOfObjectTypeAndEmptyObject(right, readonly); if (right.flags & TypeFlags.Union) { return checkCrossProductUnion([left, right]) ? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)) : errorType; } if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) { return left; } if (isGenericObjectType(left) || isGenericObjectType(right)) { if (isEmptyObjectType(left)) { return right; } // When the left type is an intersection, we may need to merge the last constituent of the // intersection with the right type. For example when the left type is 'T & { a: string }' // and the right type is '{ b: string }' we produce 'T & { a: string, b: string }'. if (left.flags & TypeFlags.Intersection) { const types = (left as IntersectionType).types; const lastLeft = types[types.length - 1]; if (isNonGenericObjectType(lastLeft) && isNonGenericObjectType(right)) { return getIntersectionType(concatenate(types.slice(0, types.length - 1), [getSpreadType(lastLeft, right, symbol, objectFlags, readonly)])); } } return getIntersectionType([left, right]); } const members = createSymbolTable(); const skippedPrivateMembers = new Set<__string>(); const indexInfos = left === emptyObjectType ? getIndexInfosOfType(right) : getUnionIndexInfos([left, right]); for (const rightProp of getPropertiesOfType(right)) { if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) { skippedPrivateMembers.add(rightProp.escapedName); } else if (isSpreadableProperty(rightProp)) { members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly)); } } for (const leftProp of getPropertiesOfType(left)) { if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) { continue; } if (members.has(leftProp.escapedName)) { const rightProp = members.get(leftProp.escapedName)!; const rightType = getTypeOfSymbol(rightProp); if (rightProp.flags & SymbolFlags.Optional) { const declarations = concatenate(leftProp.declarations, rightProp.declarations); const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); const result = createSymbol(flags, leftProp.escapedName); // Optimization: avoid calculating the union type if spreading into the exact same type. // This is common, e.g. spreading one options bag into another where the bags have the // same type, or have properties which overlap. If the unions are large, it may turn out // to be expensive to perform subtype reduction. const leftType = getTypeOfSymbol(leftProp); const leftTypeWithoutUndefined = removeMissingOrUndefinedType(leftType); const rightTypeWithoutUndefined = removeMissingOrUndefinedType(rightType); result.links.type = leftTypeWithoutUndefined === rightTypeWithoutUndefined ? leftType : getUnionType([leftType, rightTypeWithoutUndefined], UnionReduction.Subtype); result.links.leftSpread = leftProp; result.links.rightSpread = rightProp; result.declarations = declarations; result.links.nameType = getSymbolLinks(leftProp).nameType; members.set(leftProp.escapedName, result); } } else { members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); } } const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, sameMap(indexInfos, info => getIndexInfoWithReadonly(info, readonly))); spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; return spread; } /** We approximate own properties as non-methods plus methods that are inside the object literal */ function isSpreadableProperty(prop: Symbol): boolean { return !some(prop.declarations, isPrivateIdentifierClassElementDeclaration) && (!(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) || !prop.declarations?.some(decl => isClassLike(decl.parent))); } function getSpreadSymbol(prop: Symbol, readonly: boolean) { const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); if (!isSetonlyAccessor && readonly === isReadonlySymbol(prop)) { return prop; } const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional); const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); result.links.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); result.declarations = prop.declarations; result.links.nameType = getSymbolLinks(prop).nameType; result.links.syntheticOrigin = prop; return result; } function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) { return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration, info.components) : info; } function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol?: Symbol, regularType?: LiteralType) { const type = createTypeWithSymbol(flags, symbol!) as LiteralType; type.value = value; type.regularType = regularType || type; return type; } function getFreshTypeOfLiteralType(type: Type): Type { if (type.flags & TypeFlags.Freshable) { if (!(type as FreshableType).freshType) { const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType); freshType.freshType = freshType; (type as FreshableType).freshType = freshType; } return (type as FreshableType).freshType; } return type; } function getRegularTypeOfLiteralType(type: Type): Type { return type.flags & TypeFlags.Freshable ? (type as FreshableType).regularType : type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) : type; } function isFreshLiteralType(type: Type) { return !!(type.flags & TypeFlags.Freshable) && (type as LiteralType).freshType === type; } function getStringLiteralType(value: string): StringLiteralType { let type; return stringLiteralTypes.get(value) || (stringLiteralTypes.set(value, type = createLiteralType(TypeFlags.StringLiteral, value) as StringLiteralType), type); } function getNumberLiteralType(value: number): NumberLiteralType { let type; return numberLiteralTypes.get(value) || (numberLiteralTypes.set(value, type = createLiteralType(TypeFlags.NumberLiteral, value) as NumberLiteralType), type); } function getBigIntLiteralType(value: PseudoBigInt): BigIntLiteralType { let type; const key = pseudoBigIntToString(value); return bigIntLiteralTypes.get(key) || (bigIntLiteralTypes.set(key, type = createLiteralType(TypeFlags.BigIntLiteral, value) as BigIntLiteralType), type); } function getEnumLiteralType(value: string | number, enumId: number, symbol: Symbol): LiteralType { let type; const key = `${enumId}${typeof value === "string" ? "@" : "#"}${value}`; const flags = TypeFlags.EnumLiteral | (typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.NumberLiteral); return enumLiteralTypes.get(key) || (enumLiteralTypes.set(key, type = createLiteralType(flags, value, symbol)), type); } function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { if (node.literal.kind === SyntaxKind.NullKeyword) { return nullType; } const links = getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal)); } return links.resolvedType; } function createUniqueESSymbolType(symbol: Symbol) { const type = createTypeWithSymbol(TypeFlags.UniqueESSymbol, symbol) as UniqueESSymbolType; type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String; return type; } function getESSymbolLikeTypeForNode(node: Node) { if (isInJSFile(node) && isJSDocTypeExpression(node)) { const host = getJSDocHost(node); if (host) { node = getSingleVariableOfVariableStatement(host) || host; } } if (isValidESSymbolDeclaration(node)) { const symbol = isCommonJsExportPropertyAssignment(node) ? getSymbolOfNode((node as BinaryExpression).left) : getSymbolOfNode(node); if (symbol) { const links = getSymbolLinks(symbol); return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol)); } } return esSymbolType; } function getThisType(node: Node): Type { const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); const parent = container && container.parent; if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { if ( !isStatic(container) && (!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body)) ) { return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; } } // inside x.prototype = { ... } if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; } // /** @return {this} */ // x.prototype.m = function() { ... } const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined; if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!; } // inside constructor function C() { ... } if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(container)).thisType!; } error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); return errorType; } function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = getThisType(node); } return links.resolvedType; } function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) { return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type); } function getArrayElementTypeNode(node: TypeNode): TypeNode | undefined { switch (node.kind) { case SyntaxKind.ParenthesizedType: return getArrayElementTypeNode((node as ParenthesizedTypeNode).type); case SyntaxKind.TupleType: if ((node as TupleTypeNode).elements.length === 1) { node = (node as TupleTypeNode).elements[0]; if (node.kind === SyntaxKind.RestType || node.kind === SyntaxKind.NamedTupleMember && (node as NamedTupleMember).dotDotDotToken) { return getArrayElementTypeNode((node as RestTypeNode | NamedTupleMember).type); } } break; case SyntaxKind.ArrayType: return (node as ArrayTypeNode).elementType; } return undefined; } function getTypeFromNamedTupleTypeNode(node: NamedTupleMember): Type { const links = getNodeLinks(node); return links.resolvedType || (links.resolvedType = node.dotDotDotToken ? getTypeFromRestTypeNode(node) : addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true, !!node.questionToken)); } function getTypeFromTypeNode(node: TypeNode): Type { return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node); } function getTypeFromTypeNodeWorker(node: TypeNode): Type { switch (node.kind) { case SyntaxKind.AnyKeyword: case SyntaxKind.JSDocAllType: case SyntaxKind.JSDocUnknownType: return anyType; case SyntaxKind.UnknownKeyword: return unknownType; case SyntaxKind.StringKeyword: return stringType; case SyntaxKind.NumberKeyword: return numberType; case SyntaxKind.BigIntKeyword: return bigintType; case SyntaxKind.BooleanKeyword: return booleanType; case SyntaxKind.SymbolKeyword: return esSymbolType; case SyntaxKind.VoidKeyword: return voidType; case SyntaxKind.UndefinedKeyword: return undefinedType; case SyntaxKind.NullKeyword as TypeNodeSyntaxKind: // TODO(rbuckton): `NullKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service. return nullType; case SyntaxKind.NeverKeyword: return neverType; case SyntaxKind.ObjectKeyword: return node.flags & NodeFlags.JavaScriptFile && !noImplicitAny ? anyType : nonPrimitiveType; case SyntaxKind.IntrinsicKeyword: return intrinsicMarkerType; case SyntaxKind.ThisType: case SyntaxKind.ThisKeyword as TypeNodeSyntaxKind: // TODO(rbuckton): `ThisKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service and because of `isPartOfTypeNode`. return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode); case SyntaxKind.LiteralType: return getTypeFromLiteralTypeNode(node as LiteralTypeNode); case SyntaxKind.TypeReference: return getTypeFromTypeReference(node as TypeReferenceNode); case SyntaxKind.TypePredicate: return (node as TypePredicateNode).assertsModifier ? voidType : booleanType; case SyntaxKind.ExpressionWithTypeArguments: return getTypeFromTypeReference(node as ExpressionWithTypeArguments); case SyntaxKind.TypeQuery: return getTypeFromTypeQueryNode(node as TypeQueryNode); case SyntaxKind.ArrayType: case SyntaxKind.TupleType: return getTypeFromArrayOrTupleTypeNode(node as ArrayTypeNode | TupleTypeNode); case SyntaxKind.OptionalType: return getTypeFromOptionalTypeNode(node as OptionalTypeNode); case SyntaxKind.UnionType: return getTypeFromUnionTypeNode(node as UnionTypeNode); case SyntaxKind.IntersectionType: return getTypeFromIntersectionTypeNode(node as IntersectionTypeNode); case SyntaxKind.JSDocNullableType: return getTypeFromJSDocNullableTypeNode(node as JSDocNullableType); case SyntaxKind.JSDocOptionalType: return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type)); case SyntaxKind.NamedTupleMember: return getTypeFromNamedTupleTypeNode(node as NamedTupleMember); case SyntaxKind.ParenthesizedType: case SyntaxKind.JSDocNonNullableType: case SyntaxKind.JSDocTypeExpression: return getTypeFromTypeNode((node as ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression | NamedTupleMember).type); case SyntaxKind.RestType: return getTypeFromRestTypeNode(node as RestTypeNode); case SyntaxKind.JSDocVariadicType: return getTypeFromJSDocVariadicType(node as JSDocVariadicType); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.TypeLiteral: case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocFunctionType: case SyntaxKind.JSDocSignature: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node as TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature); case SyntaxKind.TypeOperator: return getTypeFromTypeOperatorNode(node as TypeOperatorNode); case SyntaxKind.IndexedAccessType: return getTypeFromIndexedAccessTypeNode(node as IndexedAccessTypeNode); case SyntaxKind.MappedType: return getTypeFromMappedTypeNode(node as MappedTypeNode); case SyntaxKind.ConditionalType: return getTypeFromConditionalTypeNode(node as ConditionalTypeNode); case SyntaxKind.InferType: return getTypeFromInferTypeNode(node as InferTypeNode); case SyntaxKind.TemplateLiteralType: return getTypeFromTemplateTypeNode(node as TemplateLiteralTypeNode); case SyntaxKind.ImportType: return getTypeFromImportTypeNode(node as ImportTypeNode); // This function assumes that an identifier, qualified name, or property access expression is a type expression // Callers should first ensure this by calling `isPartOfTypeNode` // TODO(rbuckton): These aren't valid TypeNodes, but we treat them as such because of `isPartOfTypeNode`, which returns `true` for things that aren't `TypeNode`s. case SyntaxKind.Identifier as TypeNodeSyntaxKind: case SyntaxKind.QualifiedName as TypeNodeSyntaxKind: case SyntaxKind.PropertyAccessExpression as TypeNodeSyntaxKind: const symbol = getSymbolAtLocation(node); return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; default: return errorType; } } function instantiateList(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[]; function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined; function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined { if (items && items.length) { for (let i = 0; i (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 Type, debugInfo: () => string): TypeMapper { return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Function, func, debugInfo: Debug.isDebugging ? debugInfo : undefined }); } function makeDeferredTypeMapper(sources: readonly TypeParameter[], targets: (() => Type)[]) { return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Deferred, sources, targets }); } function makeCompositeTypeMapper(kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { return Debug.attachDebugPrototypeIfDebug({ kind, mapper1, mapper2 }); } function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper { return createTypeMapper(sources, /*targets*/ undefined); } /** * Maps forward-references to later types parameters to the empty object type. * This is used during inference when instantiating type parameter defaults. */ function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { const forwardInferences = context.inferences.slice(index); return createTypeMapper(map(forwardInferences, i => i.typeParameter), map(forwardInferences, () => unknownType)); } /** * Return a type mapper that combines the context's return mapper with a mapper that erases any additional type parameters * to their inferences at the time of creation. */ function createOuterReturnMapper(context: InferenceContext) { return context.outerReturnMapper ??= mergeTypeMappers(context.returnMapper, cloneInferenceContext(context).mapper); } function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; } function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2; } function prependTypeMapping(source: Type, target: Type, mapper: TypeMapper | undefined) { return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, makeUnaryTypeMapper(source, target), mapper); } function appendTypeMapping(mapper: TypeMapper | undefined, source: Type, target: Type) { return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, mapper, makeUnaryTypeMapper(source, target)); } function getRestrictiveTypeParameter(tp: TypeParameter) { return !tp.constraint && !getConstraintDeclaration(tp) || tp.constraint === noConstraintType ? tp : tp.restrictiveInstantiation || ( tp.restrictiveInstantiation = createTypeParameter(tp.symbol), (tp.restrictiveInstantiation as TypeParameter).constraint = noConstraintType, tp.restrictiveInstantiation ); } function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { const result = createTypeParameter(typeParameter.symbol); result.target = typeParameter; return result; } function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate { return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper)); } function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { let freshTypeParameters: TypeParameter[] | undefined; if (signature.typeParameters && !eraseTypeParameters) { // First create a fresh set of type parameters, then include a mapping from the old to the // new type parameters in the mapper function. Finally store this mapper in the new type // parameters such that we can use it when instantiating constraints. freshTypeParameters = map(signature.typeParameters, cloneTypeParameter); mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); for (const tp of freshTypeParameters) { tp.mapper = mapper; } } // Don't compute resolvedReturnType and resolvedTypePredicate now, // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) // See GH#17600. const result = createSignature(signature.declaration, freshTypeParameters, signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), instantiateList(signature.parameters, mapper, instantiateSymbol), /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, signature.minArgumentCount, signature.flags & SignatureFlags.PropagatingFlags); result.target = signature; result.mapper = mapper; return result; } function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { const links = getSymbolLinks(symbol); // If the type of the symbol is already resolved, and if that type could not possibly // be affected by instantiation, simply return the symbol itself. if (links.type && !couldContainTypeVariables(links.type)) { if (!(symbol.flags & SymbolFlags.SetAccessor)) { return symbol; } // If we're a setter, check writeType. if (links.writeType && !couldContainTypeVariables(links.writeType)) { return symbol; } } if (getCheckFlags(symbol) & CheckFlags.Instantiated) { // If symbol being instantiated is itself a instantiation, fetch the original target and combine the // type mappers. This ensures that original type identities are properly preserved and that aliases // always reference a non-aliases. symbol = links.target!; mapper = combineTypeMappers(links.mapper, mapper); } // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and // also transient so that we can just store data on it directly. const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter)); result.declarations = symbol.declarations; result.parent = symbol.parent; result.links.target = symbol; result.links.mapper = mapper; if (symbol.valueDeclaration) { result.valueDeclaration = symbol.valueDeclaration; } if (links.nameType) { result.links.nameType = links.nameType; } return result; } function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { const declaration = type.objectFlags & ObjectFlags.Reference ? (type as TypeReference).node! : type.objectFlags & ObjectFlags.InstantiationExpressionType ? (type as InstantiationExpressionType).node : type.symbol.declarations![0]; const links = getNodeLinks(declaration); const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! as DeferredTypeReference : type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; let typeParameters = links.outerTypeParameters; if (!typeParameters) { // The first time an anonymous type is instantiated we compute and store a list of the type // parameters that are in scope (and therefore potentially referenced). For type literals that // aren't the right hand side of a generic type alias declaration we optimize by reducing the // set of type parameters to those that are possibly referenced in the literal. let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); if (isJSConstructor(declaration)) { const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters); outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); } typeParameters = outerTypeParameters || emptyArray; const allDeclarations = type.objectFlags & (ObjectFlags.Reference | ObjectFlags.InstantiationExpressionType) ? [declaration] : type.symbol.declarations!; typeParameters = (target.objectFlags & (ObjectFlags.Reference | ObjectFlags.InstantiationExpressionType) || target.symbol.flags & SymbolFlags.Method || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ? filter(typeParameters, tp => some(allDeclarations, d => isTypeParameterPossiblyReferenced(tp, d))) : typeParameters; links.outerTypeParameters = typeParameters; } if (typeParameters.length) { // We are instantiating an anonymous type that has one or more type parameters in scope. Apply the // mapper to the type parameters to produce the effective list of type arguments, and compute the // instantiation cache key from the type IDs of the type arguments. const combinedMapper = combineTypeMappers(type.mapper, mapper); const typeArguments = map(typeParameters, t => getMappedType(t, combinedMapper)); const newAliasSymbol = aliasSymbol || type.aliasSymbol; const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); const id = getTypeListId(typeArguments) + getAliasId(newAliasSymbol, newAliasTypeArguments); if (!target.instantiations) { target.instantiations = new Map(); target.instantiations.set(getTypeListId(typeParameters) + getAliasId(target.aliasSymbol, target.aliasTypeArguments), target); } let result = target.instantiations.get(id); if (!result) { let newMapper = createTypeMapper(typeParameters, typeArguments); if (target.objectFlags & ObjectFlags.SingleSignatureType && mapper) { newMapper = combineTypeMappers(newMapper, mapper); } result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((type as DeferredTypeReference).target, (type as DeferredTypeReference).node, newMapper, newAliasSymbol, newAliasTypeArguments) : target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(target as MappedType, newMapper, newAliasSymbol, newAliasTypeArguments) : instantiateAnonymousType(target, newMapper, newAliasSymbol, newAliasTypeArguments); target.instantiations.set(id, result); // Set cached result early in case we recursively invoke instantiation while eagerly computing type variable visibility below const resultObjectFlags = getObjectFlags(result); if (result.flags & TypeFlags.ObjectFlagsType && !(resultObjectFlags & ObjectFlags.CouldContainTypeVariablesComputed)) { const resultCouldContainTypeVariables = some(typeArguments, couldContainTypeVariables); // one of the input type arguments might be or contain the result if (!(getObjectFlags(result) & ObjectFlags.CouldContainTypeVariablesComputed)) { // if `result` is one of the object types we tried to make (it may not be, due to how `instantiateMappedType` works), we can carry forward the type variable containment check from the input type arguments if (resultObjectFlags & (ObjectFlags.Mapped | ObjectFlags.Anonymous | ObjectFlags.Reference)) { (result as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariables : 0); } // If none of the type arguments for the outer type parameters contain type variables, it follows // that the instantiated type doesn't reference type variables. // Intrinsics have `CouldContainTypeVariablesComputed` pre-set, so this should only cover unions and intersections resulting from `instantiateMappedType` else { (result as ObjectFlagsType).objectFlags |= !resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariablesComputed : 0; } } } } return result; } return type; } function maybeTypeParameterReference(node: Node) { return !(node.parent.kind === SyntaxKind.TypeReference && (node.parent as TypeReferenceNode).typeArguments && node === (node.parent as TypeReferenceNode).typeName || node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier); } function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) { // If the type parameter doesn't have exactly one declaration, if there are intervening statement blocks // between the node and the type parameter declaration, if the node contains actual references to the // type parameter, or if the node contains type queries that we can't prove couldn't contain references to the type parameter, // we consider the type parameter possibly referenced. if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) { const container = tp.symbol.declarations[0].parent; for (let n = node; n !== container; n = n.parent) { if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n as ConditionalTypeNode).extendsType, containsReference)) { return true; } } return containsReference(node); } return true; function containsReference(node: Node): boolean { switch (node.kind) { case SyntaxKind.ThisType: return !!tp.isThisType; case SyntaxKind.Identifier: return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) && getTypeFromTypeNodeWorker(node as TypeNode) === tp; // use worker because we're looking for === equality case SyntaxKind.TypeQuery: const entityName = (node as TypeQueryNode).exprName; const firstIdentifier = getFirstIdentifier(entityName); if (!isThisIdentifier(firstIdentifier)) { // Don't attempt to analyze typeof this.xxx const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier); const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called const tpScope = tpDeclaration.kind === SyntaxKind.TypeParameter ? tpDeclaration.parent : // Type parameter is a regular type parameter, e.g. foo tp.isThisType ? tpDeclaration : // Type parameter is the this type, and its declaration is the class declaration. undefined; // Type parameter's declaration was unrecognized, e.g. comes from JSDoc annotation. if (firstIdentifierSymbol.declarations && tpScope) { return some(firstIdentifierSymbol.declarations, idDecl => isNodeDescendantOf(idDecl, tpScope)) || some((node as TypeQueryNode).typeArguments, containsReference); } } return true; case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: return !(node as FunctionLikeDeclaration).type && !!(node as FunctionLikeDeclaration).body || some((node as FunctionLikeDeclaration).typeParameters, containsReference) || some((node as FunctionLikeDeclaration).parameters, containsReference) || !!(node as FunctionLikeDeclaration).type && containsReference((node as FunctionLikeDeclaration).type!); } return !!forEachChild(node, containsReference); } } function getHomomorphicTypeVariable(type: MappedType) { const constraintType = getConstraintTypeFromMappedType(type); if (constraintType.flags & TypeFlags.Index) { const typeVariable = getActualTypeVariable((constraintType as IndexType).type); if (typeVariable.flags & TypeFlags.TypeParameter) { return typeVariable as TypeParameter; } } return undefined; } function instantiateMappedType(type: MappedType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { // For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping // operation depends on T as follows: // * If T is a primitive type no mapping is performed and the result is simply T. // * If T is a union type we distribute the mapped type over the union. // * If T is an array we map to an array where the element type has been transformed. // * If T is a tuple we map to a tuple where the element types have been transformed. // * If T is an intersection of array or tuple types we map to an intersection of transformed array or tuple types. // * Otherwise we map to an object type where the type of each property has been transformed. // For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } | // { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce // { [P in keyof A]: X } | undefined. const typeVariable = getHomomorphicTypeVariable(type); if (typeVariable) { const mappedTypeVariable = instantiateType(typeVariable, mapper); if (typeVariable !== mappedTypeVariable) { return mapTypeWithAlias(getReducedType(mappedTypeVariable), instantiateConstituent, aliasSymbol, aliasTypeArguments); } } // If the constraint type of the instantiation is the wildcard type, return the wildcard type. return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments); function instantiateConstituent(t: Type): Type { if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) { if (!type.declaration.nameType) { let constraint; if ( isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable!, TypeSystemPropertyName.ImmediateBaseConstraint) = { [K in keyof T]: K }; // type Foo = Keys; // ["0", "1", ...Keys, number] // const elementFlags = tupleType.target.elementFlags; const fixedLength = tupleType.target.fixedLength; const fixedMapper = fixedLength ? prependTypeMapping(typeVariable, tupleType, mapper) : mapper; const newElementTypes = map(getElementTypes(tupleType), (type, i) => { const flags = elementFlags[i]; return i f & ElementFlags.Required ? ElementFlags.Optional : f) : modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : elementFlags; const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, getMappedTypeModifiers(mappedType)); return contains(newElementTypes, errorType) ? errorType : createTupleType(newElementTypes, newElementFlags, newReadonly, tupleType.target.labeledElementDeclarations); } function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); return isErrorType(elementType) ? errorType : createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); } function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key); const propType = instantiateType(getTemplateTypeFromMappedType(type.target as MappedType || type), templateMapper); const modifiers = getMappedTypeModifiers(type); return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : propType; } function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): AnonymousType { Debug.assert(type.symbol, "anonymous type must have symbol to be instantiated"); const result = createObjectType(type.objectFlags & ~(ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.CouldContainTypeVariables) | ObjectFlags.Instantiated, type.symbol) as AnonymousType; if (type.objectFlags & ObjectFlags.Mapped) { (result as MappedType).declaration = (type as MappedType).declaration; // C.f. instantiateSignature const origTypeParameter = getTypeParameterFromMappedType(type as MappedType); const freshTypeParameter = cloneTypeParameter(origTypeParameter); (result as MappedType).typeParameter = freshTypeParameter; mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper); freshTypeParameter.mapper = mapper; } if (type.objectFlags & ObjectFlags.InstantiationExpressionType) { (result as InstantiationExpressionType).node = (type as InstantiationExpressionType).node; } result.target = type; result.mapper = mapper; result.aliasSymbol = aliasSymbol || type.aliasSymbol; result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); result.objectFlags |= result.aliasTypeArguments ? getPropagatingFlagsOfTypes(result.aliasTypeArguments) : 0; return result; } function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { const root = type.root; if (root.outerTypeParameters) { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the // mapper to the type parameters to produce the effective list of type arguments, and compute the // instantiation cache key from the type IDs of the type arguments. const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper)); const id = (forConstraint ? "C" : "") + getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); let result = root.instantiations!.get(id); if (!result) { const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkType = root.checkType; const distributionType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined; // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). result = distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never) ? mapTypeWithAlias(distributionType, t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), aliasSymbol, aliasTypeArguments) : getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments); root.instantiations!.set(id, result); } return result; } return type; } function instantiateType(type: Type, mapper: TypeMapper | undefined): Type; function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined; function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined { return type && mapper ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; } function instantiateTypeWithAlias(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { if (!couldContainTypeVariables(type)) { return type; } if (instantiationDepth === 100 || instantiationCount >= 5000000) { // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } const index = findActiveMapper(mapper); if (index === -1) { pushActiveMapper(mapper); } const key = type.id + getAliasId(aliasSymbol, aliasTypeArguments); const mapperCache = activeTypeMappersCaches[index !== -1 ? index : activeTypeMappersCount - 1]; const cached = mapperCache.get(key); if (cached) { return cached; } totalInstantiationCount++; instantiationCount++; instantiationDepth++; const result = instantiateTypeWorker(type, mapper, aliasSymbol, aliasTypeArguments); if (index === -1) { popActiveMapper(); } else { mapperCache.set(key, result); } instantiationDepth--; return result; } function instantiateTypeWorker(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { const flags = type.flags; if (flags & TypeFlags.TypeParameter) { return getMappedType(type, mapper); } if (flags & TypeFlags.Object) { const objectFlags = (type as ObjectType).objectFlags; if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; } if (objectFlags & ObjectFlags.ReverseMapped) { return instantiateReverseMappedType(type as ReverseMappedType, mapper); } return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); } return type; } if (flags & TypeFlags.UnionOrIntersection) { const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; const newTypes = instantiateTypes(types, mapper); if (newTypes === types && aliasSymbol === type.aliasSymbol) { return type; } const newAliasSymbol = aliasSymbol || type.aliasSymbol; const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? getIntersectionType(newTypes, IntersectionFlags.None, newAliasSymbol, newAliasTypeArguments) : getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); } if (flags & TypeFlags.Index) { return getIndexType(instantiateType((type as IndexType).type, mapper)); } if (flags & TypeFlags.TemplateLiteral) { return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); } if (flags & TypeFlags.StringMapping) { return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); } if (flags & TypeFlags.IndexedAccess) { const newAliasSymbol = aliasSymbol || type.aliasSymbol; const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); } if (flags & TypeFlags.Conditional) { return getConditionalTypeInstantiation( type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), /*forConstraint*/ false, aliasSymbol, aliasTypeArguments, ); } if (flags & TypeFlags.Substitution) { const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); if (isNoInferType(type)) { return getNoInferType(newBaseType); } const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); // A substitution type originates in the true branch of a conditional type and can be resolved // to just the base type in the same cases as the conditional type resolves to its true branch // (because the base type is then known to satisfy the constraint). if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { return getSubstitutionType(newBaseType, newConstraint); } if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { return newBaseType; } return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); } return type; } function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { const innerMappedType = instantiateType(type.mappedType, mapper); if (!(getObjectFlags(innerMappedType) & ObjectFlags.Mapped)) { return type; } const innerIndexType = instantiateType(type.constraintType, mapper); if (!(innerIndexType.flags & TypeFlags.Index)) { return type; } const instantiated = inferTypeForHomomorphicMappedType( instantiateType(type.source, mapper), innerMappedType as MappedType, innerIndexType as IndexType, ); if (instantiated) { return instantiated; } return type; // Nested invocation of `inferTypeForHomomorphicMappedType` or the `source` instantiated into something unmappable } function getPermissiveInstantiation(type: Type) { return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); } function getRestrictiveInstantiation(type: Type) { if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { return type; } if (type.restrictiveInstantiation) { return type.restrictiveInstantiation; } type.restrictiveInstantiation = instantiateType(type, restrictiveMapper); // We set the following so we don't attempt to set the restrictive instance of a restrictive instance // which is redundant - we'll produce new type identities, but all type params have already been mapped. // This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint" // assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters // are constrained to `unknown` and produce tons of false positives/negatives! type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation; return type.restrictiveInstantiation; } function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) { return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration, info.components); } // Returns true if the given expression contains (at any level of nesting) a function or arrow expression // that is subject to contextual typing. function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); switch (node.kind) { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type return isContextSensitiveFunctionLikeDeclaration(node as FunctionExpression | ArrowFunction | MethodDeclaration); case SyntaxKind.ObjectLiteralExpression: return some((node as ObjectLiteralExpression).properties, isContextSensitive); case SyntaxKind.ArrayLiteralExpression: return some((node as ArrayLiteralExpression).elements, isContextSensitive); case SyntaxKind.ConditionalExpression: return isContextSensitive((node as ConditionalExpression).whenTrue) || isContextSensitive((node as ConditionalExpression).whenFalse); case SyntaxKind.BinaryExpression: return ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && (isContextSensitive((node as BinaryExpression).left) || isContextSensitive((node as BinaryExpression).right)); case SyntaxKind.PropertyAssignment: return isContextSensitive((node as PropertyAssignment).initializer); case SyntaxKind.ParenthesizedExpression: return isContextSensitive((node as ParenthesizedExpression).expression); case SyntaxKind.JsxAttributes: return some((node as JsxAttributes).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive); case SyntaxKind.JsxAttribute: { // If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive. const { initializer } = node as JsxAttribute; return !!initializer && isContextSensitive(initializer); } case SyntaxKind.JsxExpression: { // It is possible to that node.expression is undefined (e.g

) const { expression } = node as JsxExpression; return !!expression && isContextSensitive(expression); } } return false; } function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node); } function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) { if (node.typeParameters || getEffectiveReturnTypeNode(node) || !node.body) { return false; } if (node.body.kind !== SyntaxKind.Block) { return isContextSensitive(node.body); } return !!forEachReturnStatement(node.body as Block, statement => !!statement.expression && isContextSensitive(statement.expression)); } function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func); } function getTypeWithoutSignatures(type: Type): Type { if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type as ObjectType); if (resolved.constructSignatures.length || resolved.callSignatures.length) { const result = createObjectType(ObjectFlags.Anonymous, type.symbol); result.members = resolved.members; result.properties = resolved.properties; result.callSignatures = emptyArray; result.constructSignatures = emptyArray; result.indexInfos = emptyArray; return result; } } else if (type.flags & TypeFlags.Intersection) { return getIntersectionType(map((type as IntersectionType).types, getTypeWithoutSignatures)); } return type; } // TYPE CHECKING function isTypeIdenticalTo(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, identityRelation); } function compareTypesIdentical(source: Type, target: Type): Ternary { return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False; } function compareTypesAssignable(source: Type, target: Type): Ternary { return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False; } function compareTypesSubtypeOf(source: Type, target: Type): Ternary { return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False; } function isTypeSubtypeOf(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, subtypeRelation); } function isTypeStrictSubtypeOf(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, strictSubtypeRelation); } function isTypeAssignableTo(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, assignableRelation); } // An object type S is considered to be derived from an object type T if // S is a union type and every constituent of S is derived from T, // T is a union type and S is derived from at least one constituent of T, or // S is an intersection type and some constituent of S is derived from T, or // S is a type variable with a base constraint that is derived from T, or // T is {} and S is an object-like type (ensuring {} is less derived than Object), or // T is one of the global types Object and Function and S is a subtype of T, or // T occurs directly or indirectly in an 'extends' clause of S. // Note that this check ignores type parameters and only considers the // inheritance hierarchy. function isTypeDerivedFrom(source: Type, target: Type): boolean { return source.flags & TypeFlags.Union ? every((source as UnionType).types, t => isTypeDerivedFrom(t, target)) : target.flags & TypeFlags.Union ? some((target as UnionType).types, t => isTypeDerivedFrom(source, t)) : source.flags & TypeFlags.Intersection ? some((source as IntersectionType).types, t => isTypeDerivedFrom(t, target)) : source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : isEmptyAnonymousObjectType(target) ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) && !isEmptyAnonymousObjectType(source) : target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) : hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType)); } /** * This is *not* a bi-directional relationship. * If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'. * * A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T. * It is used to check following cases: * - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`). * - the types of `case` clause expressions and their respective `switch` expressions. * - the type of an expression in a type assertion with the type being asserted. */ function isTypeComparableTo(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, comparableRelation); } function areTypesComparable(type1: Type, type2: Type): boolean { return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1); } function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { errors?: Diagnostic[]; }): boolean { return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject); } /** * Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to * attempt to issue more specific errors on, for example, specific object literal properties or tuple members. */ function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined); } function checkTypeRelatedToAndOptionallyElaborate( source: Type, target: Type, relation: Map, errorNode: Node | undefined, expr: Expression | undefined, headMessage: DiagnosticMessage | undefined, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: ErrorOutputContainer | undefined, ): boolean { if (isTypeRelatedTo(source, target, relation)) return true; if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); } return false; } function isOrHasGenericConditional(type: Type): boolean { return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional))); } function elaborateError( node: Expression | undefined, source: Type, target: Type, relation: Map, headMessage: DiagnosticMessage | undefined, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: ErrorOutputContainer | undefined, ): boolean { if (!node || isOrHasGenericConditional(target)) return false; if ( !checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined) && elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer) ) { return true; } switch (node.kind) { case SyntaxKind.AsExpression: if (!isConstAssertion(node)) { break; } // fallthrough case SyntaxKind.JsxExpression: case SyntaxKind.ParenthesizedExpression: return elaborateError((node as AsExpression | ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); case SyntaxKind.BinaryExpression: switch ((node as BinaryExpression).operatorToken.kind) { case SyntaxKind.EqualsToken: case SyntaxKind.CommaToken: return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); } break; case SyntaxKind.ObjectLiteralExpression: return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); case SyntaxKind.ArrayLiteralExpression: return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); case SyntaxKind.JsxAttributes: return elaborateJsxComponents(node as JsxAttributes, source, target, relation, containingMessageChain, errorOutputContainer); case SyntaxKind.ArrowFunction: return elaborateArrowFunction(node as ArrowFunction, source, target, relation, containingMessageChain, errorOutputContainer); } return false; } function elaborateDidYouMeanToCallOrConstruct( node: Expression, source: Type, target: Type, relation: Map, headMessage: DiagnosticMessage | undefined, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: ErrorOutputContainer | undefined, ): boolean { const callSignatures = getSignaturesOfType(source, SignatureKind.Call); const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct); for (const signatures of [constructSignatures, callSignatures]) { if ( some(signatures, s => { const returnType = getReturnTypeOfSignature(s); return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined); }) ) { const resultObj: { errors?: Diagnostic[]; } = errorOutputContainer || {}; checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj); const diagnostic = resultObj.errors![resultObj.errors!.length - 1]; addRelatedInfo( diagnostic, createDiagnosticForNode( node, signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression, ), ); return true; } } return false; } function elaborateArrowFunction( node: ArrowFunction, source: Type, target: Type, relation: Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: ErrorOutputContainer | undefined, ): boolean { // Don't elaborate blocks if (isBlock(node.body)) { return false; } // Or functions with annotated parameter types if (some(node.parameters, hasType)) { return false; } const sourceSig = getSingleCallSignature(source); if (!sourceSig) { return false; } const targetSignatures = getSignaturesOfType(target, SignatureKind.Call); if (!length(targetSignatures)) { return false; } const returnExpression = node.body; const sourceReturn = getReturnTypeOfSignature(sourceSig); const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature)); if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) { const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); if (elaborated) { return elaborated; } const resultObj: { errors?: Diagnostic[]; } = errorOutputContainer || {}; checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*headMessage*/ undefined, containingMessageChain, resultObj); if (resultObj.errors) { if (target.symbol && length(target.symbol.declarations)) { addRelatedInfo( resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( target.symbol.declarations![0], Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature, ), ); } if ( (getFunctionFlags(node) & FunctionFlags.Async) === 0 // exclude cases where source itself is promisy - this way we don't make a suggestion when relating // an IPromise and a Promise that are slightly different && !getTypeOfPropertyOfType(sourceReturn, "then" as __String) && checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined) ) { addRelatedInfo( resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( node, Diagnostics.Did_you_mean_to_mark_this_function_as_async, ), ); } return true; } } return false; } function getBestMatchIndexedAccessTypeOrUndefined(source: Type, target: Type, nameType: Type) { const idx = getIndexedAccessTypeOrUndefined(target, nameType); if (idx) { return idx; } if (target.flags & TypeFlags.Union) { const best = getBestMatchingType(source, target as UnionType); if (best) { return getIndexedAccessTypeOrUndefined(best, nameType); } } } function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { pushContextualType(next, sourcePropType, /*isCache*/ false); const result = checkExpressionForMutableLocation(next, CheckMode.Contextual); popContextualType(); return result; } type ElaborationIterator = IterableIterator; /** * For every element returned from the iterator, checks that element to issue an error on a property of that element's type * If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError` * Otherwise, we issue an error on _every_ element which fail the assignability check */ function elaborateElementwise( iterator: ElaborationIterator, source: Type, target: Type, relation: Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: ErrorOutputContainer | undefined, ) { // Assignability failure - check each prop individually, and if that fails, fall back on the bad error span let reportedError = false; for (const value of iterator) { const { errorNode: prop, innerExpression: next, nameType, errorMessage } = value; let targetPropType = getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType); if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); if (!sourcePropType) continue; const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined); if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); reportedError = true; if (!elaborated) { // Issue error on the prop itself, since the prop couldn't elaborate the error const resultObj: { errors?: Diagnostic[]; } = errorOutputContainer || {}; // Use the expression type, if available const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType; if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) { const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType)); diagnostics.add(diag); resultObj.errors = [diag]; } else { const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional); const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional); targetPropType = removeMissingType(targetPropType, targetIsOptional); sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional); const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); if (result && specificSource !== sourcePropType) { // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); } } if (resultObj.errors) { const reportedDiag = resultObj.errors[resultObj.errors.length - 1]; const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined; let issuedElaboration = false; if (!targetProp) { const indexInfo = getApplicableIndexInfo(target, nameType); if (indexInfo && indexInfo.declaration && !getSourceFileOfNode(indexInfo.declaration).hasNoDefaultLib) { issuedElaboration = true; addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature)); } } if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) { const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations![0] : target.symbol.declarations![0]; if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) { addRelatedInfo( reportedDiag, createDiagnosticForNode( targetNode, Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1, propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType), typeToString(target), ), ); } } } } } } return reportedError; } /** * Assumes `target` type is assignable to the `Iterable` type, if `Iterable` is defined, * or that it's an array or tuple-like type, if `Iterable` is not defined. */ function elaborateIterableOrArrayLikeTargetElementwise( iterator: ElaborationIterator, source: Type, target: Type, relation: Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: ErrorOutputContainer | undefined, ) { const tupleOrArrayLikeTargetParts = filterType(target, isArrayOrTupleLikeType); const nonTupleOrArrayLikeTargetParts = filterType(target, t => !isArrayOrTupleLikeType(t)); // If `nonTupleOrArrayLikeTargetParts` is not `never`, then that should mean `Iterable` is defined. const iterationType = nonTupleOrArrayLikeTargetParts !== neverType ? getIterationTypeOfIterable(IterationUse.ForOf, IterationTypeKind.Yield, nonTupleOrArrayLikeTargetParts, /*errorNode*/ undefined) : undefined; let reportedError = false; for (let status = iterator.next(); !status.done; status = iterator.next()) { const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; let targetPropType = iterationType; const targetIndexedPropType = tupleOrArrayLikeTargetParts !== neverType ? getBestMatchIndexedAccessTypeOrUndefined(source, tupleOrArrayLikeTargetParts, nameType) : undefined; if (targetIndexedPropType && !(targetIndexedPropType.flags & TypeFlags.IndexedAccess)) { // Don't elaborate on indexes on generic variables targetPropType = iterationType ? getUnionType([iterationType, targetIndexedPropType]) : targetIndexedPropType; } if (!targetPropType) continue; let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); if (!sourcePropType) continue; const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined); if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); reportedError = true; if (!elaborated) { // Issue error on the prop itself, since the prop couldn't elaborate the error const resultObj: { errors?: Diagnostic[]; } = errorOutputContainer || {}; // Use the expression type, if available const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType; if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) { const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType)); diagnostics.add(diag); resultObj.errors = [diag]; } else { const targetIsOptional = !!(propName && (getPropertyOfType(tupleOrArrayLikeTargetParts, propName) || unknownSymbol).flags & SymbolFlags.Optional); const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional); targetPropType = removeMissingType(targetPropType, targetIsOptional); sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional); const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); if (result && specificSource !== sourcePropType) { // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); } } } } } return reportedError; } function* generateJsxAttributes(node: JsxAttributes): ElaborationIterator { if (!length(node.properties)) return; for (const prop of node.properties) { if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue; yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) }; } } function* generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator { if (!length(node.children)) return; let memberOffset = 0; for (let i = 0; i DiagnosticMessage) { switch (child.kind) { case SyntaxKind.JsxExpression: // child is of the type of the expression return { errorNode: child, innerExpression: child.expression, nameType }; case SyntaxKind.JsxText: if (child.containsOnlyTriviaWhiteSpaces) { break; // Whitespace only jsx text isn't real jsx text } // child is a string return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() }; case SyntaxKind.JsxElement: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.JsxFragment: // child is of type JSX.Element return { errorNode: child, innerExpression: child, nameType }; default: return Debug.assertNever(child, "Found invalid jsx child"); } } function elaborateJsxComponents( node: JsxAttributes, source: Type, target: Type, relation: Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: ErrorOutputContainer | undefined, ) { let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer); let invalidTextDiagnostic: DiagnosticMessage | undefined; if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) { const containingElement = node.parent.parent; const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); const childrenNameType = getStringLiteralType(childrenPropName); const childrenTargetType = getIndexedAccessType(target, childrenNameType); const validChildren = getSemanticJsxChildren(containingElement.children); if (!length(validChildren)) { return result; } const moreThanOneRealChildren = length(validChildren) > 1; let arrayLikeTargetParts: Type; let nonArrayLikeTargetParts: Type; const iterableType = getGlobalIterableType(/*reportErrors*/ false); if (iterableType !== emptyGenericType) { const anyIterable = createIterableType(anyType); arrayLikeTargetParts = filterType(childrenTargetType, t => isTypeAssignableTo(t, anyIterable)); nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isTypeAssignableTo(t, anyIterable)); } else { arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); } if (moreThanOneRealChildren) { if (arrayLikeTargetParts !== neverType) { const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); result = elaborateIterableOrArrayLikeTargetElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result; } else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { // arity mismatch result = true; const diag = error( containingElement.openingElement.tagName, Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided, childrenPropName, typeToString(childrenTargetType), ); if (errorOutputContainer && errorOutputContainer.skipLogging) { (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); } } } else { if (nonArrayLikeTargetParts !== neverType) { const child = validChildren[0]; const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic); if (elem) { result = elaborateElementwise( (function* () { yield elem; })(), source, target, relation, containingMessageChain, errorOutputContainer, ) || result; } } else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { // arity mismatch result = true; const diag = error( containingElement.openingElement.tagName, Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided, childrenPropName, typeToString(childrenTargetType), ); if (errorOutputContainer && errorOutputContainer.skipLogging) { (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); } } } } return result; function getInvalidTextualChildDiagnostic() { if (!invalidTextDiagnostic) { const tagNameText = getTextOfNode(node.parent.tagName); const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); const childrenTargetType = getIndexedAccessType(target, getStringLiteralType(childrenPropName)); const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2; invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; } return invalidTextDiagnostic; } } function* generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator { const len = length(node.elements); if (!len) return; for (let i = 0; i , containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: ErrorOutputContainer | undefined, ) { if (target.flags & (TypeFlags.Primitive | TypeFlags.Never)) return false; if (isTupleLikeType(source)) { return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer); } // recreate a tuple from the elements, if possible // Since we're re-doing the expression type, we need to reapply the contextual type pushContextualType(node, target, /*isCache*/ false); const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); popContextualType(); if (isTupleLikeType(tupleizedType)) { return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); } return false; } function* generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { if (!length(node.properties)) return; for (const prop of node.properties) { if (isSpreadAssignment(prop)) continue; const type = getLiteralTypeFromProperty(getSymbolOfDeclaration(prop), TypeFlags.StringOrNumberLiteralOrUnique); if (!type || (type.flags & TypeFlags.Never)) { continue; } switch (prop.kind) { case SyntaxKind.SetAccessor: case SyntaxKind.GetAccessor: case SyntaxKind.MethodDeclaration: case SyntaxKind.ShorthandPropertyAssignment: yield { errorNode: prop.name, innerExpression: undefined, nameType: type }; break; case SyntaxKind.PropertyAssignment: yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined }; break; default: Debug.assertNever(prop); } } } function elaborateObjectLiteral( node: ObjectLiteralExpression, source: Type, target: Type, relation: Map, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: ErrorOutputContainer | undefined, ) { if (target.flags & (TypeFlags.Primitive | TypeFlags.Never)) return false; return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer); } /** * This is *not* a bi-directional relationship. * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. */ function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); } function isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean { return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : SignatureCheckMode.None, /*reportErrors*/ false, /*errorReporter*/ undefined, /*incompatibleErrorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; } type ErrorReporter = (message: DiagnosticMessage, ...args: DiagnosticArguments) => void; /** * Returns true if `s` is `(...args: A) => R` where `A` is `any`, `any[]`, `never`, or `never[]`, and `R` is `any` or `unknown`. */ function isTopSignature(s: Signature) { if (!s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && signatureHasRestParameter(s)) { const paramType = getTypeOfParameter(s.parameters[0]); const restType = isArrayType(paramType) ? getTypeArguments(paramType)[0] : paramType; return !!(restType.flags & (TypeFlags.Any | TypeFlags.Never) && getReturnTypeOfSignature(s).flags & TypeFlags.AnyOrUnknown); } return false; } /** * See signatureRelatedTo, compareSignaturesIdentical */ function compareSignaturesRelated(source: Signature, target: Signature, checkMode: SignatureCheckMode, reportErrors: boolean, errorReporter: ErrorReporter | undefined, incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined, compareTypes: TypeComparer, reportUnreliableMarkers: TypeMapper | undefined): Ternary { // TODO (drosen): De-duplicate code between related functions. if (source === target) { return Ternary.True; } if (!(checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source)) && isTopSignature(target)) { return Ternary.True; } if (checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source) && !isTopSignature(target)) { return Ternary.False; } const targetCount = getParameterCount(target); const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); if (sourceHasMoreParameters) { if (reportErrors && !(checkMode & SignatureCheckMode.StrictArity)) { // the second condition should be redundant, because there is no error reporting when comparing signatures by strict arity // since it is only done for subtype reduction errorReporter!(Diagnostics.Target_signature_provides_too_few_arguments_Expected_0_or_more_but_got_1, getMinArgumentCount(source), targetCount); } return Ternary.False; } if (source.typeParameters && source.typeParameters !== target.typeParameters) { target = getCanonicalSignature(target); source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes); } const sourceCount = getParameterCount(source); const sourceRestType = getNonArrayRestType(source); const targetRestType = getNonArrayRestType(target); if (sourceRestType || targetRestType) { void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers); } const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; let result = Ternary.True; const sourceThisType = getThisTypeOfSignature(source); if (sourceThisType && sourceThisType !== voidType) { const targetThisType = getThisTypeOfSignature(target); if (targetThisType) { // void sources are assignable to anything. const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) || compareTypes(targetThisType, sourceThisType, reportErrors); if (!related) { if (reportErrors) { errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible); } return Ternary.False; } result &= related; } } const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount); const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; for (let i = 0; i is at least co-variant with respect to T no matter // how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions, // they naturally relate only contra-variantly). However, if the source and target parameters both have // function types with a single call signature, we know we are relating two callback parameters. In // that case it is sufficient to only relate the parameters of the signatures co-variantly because, // similar to return values, callback parameters are output positions. This means that a Promise, // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) // with respect to T. const sourceSig = checkMode & SignatureCheckMode.Callback || isInstantiatedGenericParameter(source, i) ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); const targetSig = checkMode & SignatureCheckMode.Callback || isInstantiatedGenericParameter(target, i) ? undefined : getSingleCallSignature(getNonNullableType(targetType)); const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && getTypeFacts(sourceType, TypeFacts.IsUndefinedOrNull) === getTypeFacts(targetType, TypeFacts.IsUndefinedOrNull); let related = callbacks ? compareSignaturesRelated(targetSig, sourceSig, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i { add(cb: () => T): void } // wouldn't be co-variant for T without this rule. result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || compareTypes(sourceReturnType, targetReturnType, reportErrors); if (!result && reportErrors && incompatibleErrorReporter) { incompatibleErrorReporter(sourceReturnType, targetReturnType); } } } return result; } function compareTypePredicateRelatedTo( source: TypePredicate, target: TypePredicate, reportErrors: boolean, errorReporter: ErrorReporter | undefined, compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary, ): Ternary { if (source.kind !== target.kind) { if (reportErrors) { errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard); errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } return Ternary.False; } if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) { if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) { if (reportErrors) { errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName); errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } return Ternary.False; } } const related = source.type === target.type ? Ternary.True : source.type && target.type ? compareTypes(source.type, target.type, reportErrors) : Ternary.False; if (related === Ternary.False && reportErrors) { errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } return related; } function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean { const erasedSource = getErasedSignature(implementation); const erasedTarget = getErasedSignature(overload); // First see if the return types are compatible in either direction. const sourceReturnType = getReturnTypeOfSignature(erasedSource); const targetReturnType = getReturnTypeOfSignature(erasedTarget); if ( targetReturnType === voidType || isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation) || isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation) ) { return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true); } return false; } function isEmptyResolvedType(t: ResolvedType) { return t !== anyFunctionType && t.properties.length === 0 && t.callSignatures.length === 0 && t.constructSignatures.length === 0 && t.indexInfos.length === 0; } function isEmptyObjectType(type: Type): boolean { return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(type as ObjectType)) : type.flags & TypeFlags.NonPrimitive ? true : type.flags & TypeFlags.Union ? some((type as UnionType).types, isEmptyObjectType) : type.flags & TypeFlags.Intersection ? every((type as UnionType).types, isEmptyObjectType) : false; } function isEmptyAnonymousObjectType(type: Type) { return !!(getObjectFlags(type) & ObjectFlags.Anonymous && ( (type as ResolvedType).members && isEmptyResolvedType(type as ResolvedType) || type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && getMembersOfSymbol(type.symbol).size === 0 )); } function isUnknownLikeUnionType(type: Type) { if (strictNullChecks && type.flags & TypeFlags.Union) { if (!((type as UnionType).objectFlags & ObjectFlags.IsUnknownLikeUnionComputed)) { const types = (type as UnionType).types; (type as UnionType).objectFlags |= ObjectFlags.IsUnknownLikeUnionComputed | (types.length >= 3 && types[0].flags & TypeFlags.Undefined && types[1].flags & TypeFlags.Null && some(types, isEmptyAnonymousObjectType) ? ObjectFlags.IsUnknownLikeUnion : 0); } return !!((type as UnionType).objectFlags & ObjectFlags.IsUnknownLikeUnion); } return false; } function containsUndefinedType(type: Type) { return !!((type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type).flags & TypeFlags.Undefined); } function containsNonMissingUndefinedType(type: Type) { const candidate = type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type; return !!(candidate.flags & TypeFlags.Undefined) && candidate !== missingType; } function isStringIndexSignatureOnlyType(type: Type): boolean { return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, stringType) || type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isStringIndexSignatureOnlyType) || false; } function isEnumTypeRelatedTo(source: Symbol, target: Symbol, errorReporter?: ErrorReporter) { const sourceSymbol = source.flags & SymbolFlags.EnumMember ? getParentOfSymbol(source)! : source; const targetSymbol = target.flags & SymbolFlags.EnumMember ? getParentOfSymbol(target)! : target; if (sourceSymbol === targetSymbol) { return true; } if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) { return false; } const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol); const entry = enumRelation.get(id); if (entry !== undefined && !(entry & RelationComparisonResult.Failed && errorReporter)) { return !!(entry & RelationComparisonResult.Succeeded); } const targetEnumType = getTypeOfSymbol(targetSymbol); for (const sourceProperty of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { if (sourceProperty.flags & SymbolFlags.EnumMember) { const targetProperty = getPropertyOfType(targetEnumType, sourceProperty.escapedName); if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { if (errorReporter) { errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(sourceProperty), typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); } enumRelation.set(id, RelationComparisonResult.Failed); return false; } const sourceValue = getEnumMemberValue(getDeclarationOfKind(sourceProperty, SyntaxKind.EnumMember)!).value; const targetValue = getEnumMemberValue(getDeclarationOfKind(targetProperty, SyntaxKind.EnumMember)!).value; if (sourceValue !== targetValue) { const sourceIsString = typeof sourceValue === "string"; const targetIsString = typeof targetValue === "string"; // If we have 2 enums with *known* values that differ, they are incompatible. if (sourceValue !== undefined && targetValue !== undefined) { if (errorReporter) { const escapedSource = sourceIsString ? `"${escapeString(sourceValue)}"` : sourceValue; const escapedTarget = targetIsString ? `"${escapeString(targetValue)}"` : targetValue; errorReporter(Diagnostics.Each_declaration_of_0_1_differs_in_its_value_where_2_was_expected_but_3_was_given, symbolName(targetSymbol), symbolName(targetProperty), escapedTarget, escapedSource); } enumRelation.set(id, RelationComparisonResult.Failed); return false; } // At this point we know that at least one of the values is 'undefined'. // This may mean that we have an opaque member from an ambient enum declaration, // or that we were not able to calculate it (which is basically an error). // // Either way, we can assume that it's numeric. // If the other is a string, we have a mismatch in types. if (sourceIsString || targetIsString) { if (errorReporter) { const knownStringValue = sourceValue ?? targetValue; Debug.assert(typeof knownStringValue === "string"); const escapedValue = `"${escapeString(knownStringValue)}"`; errorReporter(Diagnostics.One_value_of_0_1_is_the_string_2_and_the_other_is_assumed_to_be_an_unknown_numeric_value, symbolName(targetSymbol), symbolName(targetProperty), escapedValue); } enumRelation.set(id, RelationComparisonResult.Failed); return false; } } } } enumRelation.set(id, RelationComparisonResult.Succeeded); return true; } function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) { const s = source.flags; const t = target.flags; if (t & TypeFlags.Any || s & TypeFlags.Never || source === wildcardType) return true; if (t & TypeFlags.Unknown && !(relation === strictSubtypeRelation && s & TypeFlags.Any)) return true; if (t & TypeFlags.Never) return false; if (s & TypeFlags.StringLike && t & TypeFlags.String) return true; if ( s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral && t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) && (source as StringLiteralType).value === (target as StringLiteralType).value ) return true; if (s & TypeFlags.NumberLike && t & TypeFlags.Number) return true; if ( s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral && t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) && (source as NumberLiteralType).value === (target as NumberLiteralType).value ) return true; if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true; if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true; if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true; if ( s & TypeFlags.Enum && t & TypeFlags.Enum && source.symbol.escapedName === target.symbol.escapedName && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter) ) return true; if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) { if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; if ( s & TypeFlags.Literal && t & TypeFlags.Literal && (source as LiteralType).value === (target as LiteralType).value && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter) ) return true; } // In non-strictNullChecks mode, `undefined` and `null` are assignable to anything except `never`. // Since unions and intersections may reduce to `never`, we exclude them here. if (s & TypeFlags.Undefined && (!strictNullChecks && !(t & TypeFlags.UnionOrIntersection) || t & (TypeFlags.Undefined | TypeFlags.Void))) return true; if (s & TypeFlags.Null && (!strictNullChecks && !(t & TypeFlags.UnionOrIntersection) || t & TypeFlags.Null)) return true; if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive && !(relation === strictSubtypeRelation && isEmptyAnonymousObjectType(source) && !(getObjectFlags(source) & ObjectFlags.FreshLiteral))) return true; if (relation === assignableRelation || relation === comparableRelation) { if (s & TypeFlags.Any) return true; // Type number is assignable to any computed numeric enum type or any numeric enum literal type, and // a numeric literal type is assignable any computed numeric enum type or any numeric enum literal type // with a matching value. These rules exist such that enums can be used for bit-flag purposes. if (s & TypeFlags.Number && (t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; if ( s & TypeFlags.NumberLiteral && !(s & TypeFlags.EnumLiteral) && (t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral && (source as NumberLiteralType).value === (target as NumberLiteralType).value) ) return true; // Anything is assignable to a union containing undefined, null, and {} if (isUnknownLikeUnionType(target)) return true; } return false; } function isTypeRelatedTo(source: Type, target: Type, relation: Map) { if (isFreshLiteralType(source)) { source = (source as FreshableType).regularType; } if (isFreshLiteralType(target)) { target = (target as FreshableType).regularType; } if (source === target) { return true; } if (relation !== identityRelation) { if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) { return true; } } else if (!((source.flags | target.flags) & (TypeFlags.UnionOrIntersection | TypeFlags.IndexedAccess | TypeFlags.Conditional | TypeFlags.Substitution))) { // We have excluded types that may simplify to other forms, so types must have identical flags if (source.flags !== target.flags) return false; if (source.flags & TypeFlags.Singleton) return true; } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false)); if (related !== undefined) { return !!(related & RelationComparisonResult.Succeeded); } } if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined); } return false; } function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) { return getObjectFlags(source) & ObjectFlags.JsxAttributes && isHyphenatedJsxName(sourceProp.escapedName); } function getNormalizedType(type: Type, writing: boolean): Type { while (true) { const t = isFreshLiteralType(type) ? (type as FreshableType).regularType : isGenericTupleType(type) ? getNormalizedTupleType(type, writing) : getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).node ? createTypeReference((type as TypeReference).target, getTypeArguments(type as TypeReference)) : getSingleBaseForNonAugmentingSubtype(type) || type : type.flags & TypeFlags.UnionOrIntersection ? getNormalizedUnionOrIntersectionType(type as UnionOrIntersectionType, writing) : type.flags & TypeFlags.Substitution ? writing ? (type as SubstitutionType).baseType : getSubstitutionIntersection(type as SubstitutionType) : type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : type; if (t === type) return t; type = t; } } function getNormalizedUnionOrIntersectionType(type: UnionOrIntersectionType, writing: boolean) { const reduced = getReducedType(type); if (reduced !== type) { return reduced; } if (type.flags & TypeFlags.Intersection && shouldNormalizeIntersection(type as IntersectionType)) { // Normalization handles cases like // Partial[K] & ({} | null) ==> // Partial[K] & {} | Partial[K] & null ==> // (T[K] | undefined) & {} | (T[K] | undefined) & null ==> // T[K] & {} | undefined & {} | T[K] & null | undefined & null ==> // T[K] & {} | T[K] & null const normalizedTypes = sameMap(type.types, t => getNormalizedType(t, writing)); if (normalizedTypes !== type.types) { return getIntersectionType(normalizedTypes); } } return type; } function shouldNormalizeIntersection(type: IntersectionType) { let hasInstantiable = false; let hasNullableOrEmpty = false; for (const t of type.types) { hasInstantiable ||= !!(t.flags & TypeFlags.Instantiable); hasNullableOrEmpty ||= !!(t.flags & TypeFlags.Nullable) || isEmptyAnonymousObjectType(t); if (hasInstantiable && hasNullableOrEmpty) return true; } return false; } function getNormalizedTupleType(type: TupleTypeReference, writing: boolean): Type { const elements = getElementTypes(type); const normalizedElements = sameMap(elements, t => t.flags & TypeFlags.Simplifiable ? getSimplifiedType(t, writing) : t); return elements !== normalizedElements ? createNormalizedTupleType(type.target, normalizedElements) : type; } /** * Checks if 'source' is related to 'target' (e.g.: is a assignable to). * @param source The left-hand-side of the relation. * @param target The right-hand-side of the relation. * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. * Used as both to determine which checks are performed and as a cache of previously computed results. * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. * @param containingMessageChain A chain of errors to prepend any new errors found. * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. */ function checkTypeRelatedTo( source: Type, target: Type, relation: Map, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputContainer?: ErrorOutputContainer, ): boolean { let errorInfo: DiagnosticMessageChain | undefined; let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined; let maybeKeys: string[]; let maybeKeysSet: Set; let sourceStack: Type[]; let targetStack: Type[]; let maybeCount = 0; let sourceDepth = 0; let targetDepth = 0; let expandingFlags = ExpandingFlags.None; let overflow = false; let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid let skipParentCounter = 0; // How many errors should be skipped 'above' in the elaboration pyramid let lastSkippedInfo: [Type, Type] | undefined; let incompatibleStack: DiagnosticAndArguments[] | undefined; // In Node.js, the maximum number of elements in a map is 2^24. We limit the number of entries an invocation // of checkTypeRelatedTo can add to a relation to 1/8th of its remaining capacity. let relationCount = (16_000_000 - relation.size) >> 3; Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); const result = isRelatedTo(source, target, RecursionFlags.Both, /*reportErrors*/ !!errorNode, headMessage); if (incompatibleStack) { reportIncompatibleStack(); } if (overflow) { // Record this relation as having failed such that we don't attempt the overflowing operation again. const id = getRelationKey(source, target, /*intersectionState*/ IntersectionState.None, relation, /*ignoreConstraints*/ false); relation.set(id, RelationComparisonResult.Failed | (relationCount ) { errorInfo = saved.errorInfo; lastSkippedInfo = saved.lastSkippedInfo; incompatibleStack = saved.incompatibleStack; overrideNextErrorInfo = saved.overrideNextErrorInfo; skipParentCounter = saved.skipParentCounter; relatedInfo = saved.relatedInfo; } function captureErrorCalculationState() { return { errorInfo, lastSkippedInfo, incompatibleStack: incompatibleStack?.slice(), overrideNextErrorInfo, skipParentCounter, relatedInfo: relatedInfo?.slice() as [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined, }; } function reportIncompatibleError(message: DiagnosticMessage, ...args: DiagnosticArguments) { overrideNextErrorInfo++; // Suppress the next relation error lastSkippedInfo = undefined; // Reset skipped info cache (incompatibleStack ||= []).push([message, ...args]); } function reportIncompatibleStack() { const stack = incompatibleStack || []; incompatibleStack = undefined; const info = lastSkippedInfo; lastSkippedInfo = undefined; if (stack.length === 1) { reportError(...stack[0]); if (info) { // Actually do the last relation error reportRelationError(/*message*/ undefined, ...info); } return; } // The first error will be the innermost, while the last will be the outermost - so by popping off the end, // we can build from left to right let path = ""; const secondaryRootErrors: DiagnosticAndArguments[] = []; while (stack.length) { const [msg, ...args] = stack.pop()!; switch (msg.code) { case Diagnostics.Types_of_property_0_are_incompatible.code: { // Parenthesize a `new` if there is one if (path.indexOf("new ") === 0) { path = `(${path})`; } const str = "" + args[0]; // If leading, just print back the arg (irrespective of if it's a valid identifier) if (path.length === 0) { path = `${str}`; } // Otherwise write a dotted name if possible else if (isIdentifierText(str, getEmitScriptTarget(compilerOptions))) { path = `${path}.${str}`; } // Failing that, check if the name is already a computed name else if (str[0] === "[" && str[str.length - 1] === "]") { path = `${path}${str}`; } // And finally write out a computed name as a last resort else { path = `${path}[${str}]`; } break; } case Diagnostics.Call_signature_return_types_0_and_1_are_incompatible.code: case Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code: case Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: case Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: { if (path.length === 0) { // Don't flatten signature compatability errors at the start of a chain - instead prefer // to unify (the with no arguments bit is excessive for printback) and print them back let mappedMsg = msg; if (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { mappedMsg = Diagnostics.Call_signature_return_types_0_and_1_are_incompatible; } else if (msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { mappedMsg = Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible; } secondaryRootErrors.unshift([mappedMsg, args[0], args[1]]); } else { const prefix = (msg.code === Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code || msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) ? "new " : ""; const params = (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code || msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) ? "" : "..."; path = `${prefix}${path}(${params})`; } break; } case Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target.code: { secondaryRootErrors.unshift([Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, args[0], args[1]]); break; } case Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target.code: { secondaryRootErrors.unshift([Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, args[0], args[1], args[2]]); break; } default: return Debug.fail(`Unhandled Diagnostic: ${msg.code}`); } } if (path) { reportError( path[path.length - 1] === ")" ? Diagnostics.The_types_returned_by_0_are_incompatible_between_these_types : Diagnostics.The_types_of_0_are_incompatible_between_these_types, path, ); } else { // Remove the innermost secondary error as it will duplicate the error already reported by `reportRelationError` on entry secondaryRootErrors.shift(); } for (const [msg, ...args] of secondaryRootErrors) { const originalValue = msg.elidedInCompatabilityPyramid; msg.elidedInCompatabilityPyramid = false; // Temporarily override elision to ensure error is reported reportError(msg, ...args); msg.elidedInCompatabilityPyramid = originalValue; } if (info) { // Actually do the last relation error reportRelationError(/*message*/ undefined, ...info); } } function reportError(message: DiagnosticMessage, ...args: DiagnosticArguments): void { Debug.assert(!!errorNode); if (incompatibleStack) reportIncompatibleStack(); if (message.elidedInCompatabilityPyramid) return; if (skipParentCounter === 0) { errorInfo = chainDiagnosticMessages(errorInfo, message, ...args); } else { skipParentCounter--; } } function reportParentSkippedError(message: DiagnosticMessage, ...args: DiagnosticArguments): void { reportError(message, ...args); skipParentCounter++; } function associateRelatedInfo(info: DiagnosticRelatedInformation) { Debug.assert(!!errorInfo); if (!relatedInfo) { relatedInfo = [info]; } else { relatedInfo.push(info); } } function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) { if (incompatibleStack) reportIncompatibleStack(); const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target); let generalizedSource = source; let generalizedSourceType = sourceType; // Don't generalize on 'never' - we really want the original type // to be displayed for use-cases like 'assertNever'. if (!(target.flags & TypeFlags.Never) && isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) { generalizedSource = getBaseTypeOfLiteralType(source); Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable"); generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); } // If `target` is of indexed access type (And `source` it is not), we use the object type of `target` for better error reporting const targetFlags = target.flags & TypeFlags.IndexedAccess && !(source.flags & TypeFlags.IndexedAccess) ? (target as IndexedAccessType).objectType.flags : target.flags; if (targetFlags & TypeFlags.TypeParameter && target !== markerSuperTypeForCheck && target !== markerSubTypeForCheck) { const constraint = getBaseConstraintOfType(target); let needsOriginalSource; if (constraint && (isTypeAssignableTo(generalizedSource, constraint) || (needsOriginalSource = isTypeAssignableTo(source, constraint)))) { reportError( Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, needsOriginalSource ? sourceType : generalizedSourceType, targetType, typeToString(constraint), ); } else { errorInfo = undefined; reportError( Diagnostics._0_could_be_instantiated_with_an_arbitrary_type_which_could_be_unrelated_to_1, targetType, generalizedSourceType, ); } } if (!message) { if (relation === comparableRelation) { message = Diagnostics.Type_0_is_not_comparable_to_type_1; } else if (sourceType === targetType) { message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated; } else if (exactOptionalPropertyTypes && getExactOptionalUnassignableProperties(source, target).length) { message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; } else { if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) { const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType); if (suggestedType) { reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, typeToString(suggestedType)); return; } } message = Diagnostics.Type_0_is_not_assignable_to_type_1; } } else if ( message === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1 && exactOptionalPropertyTypes && getExactOptionalUnassignableProperties(source, target).length ) { message = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; } reportError(message, generalizedSourceType, targetType); } function tryElaborateErrorsForPrimitivesAndObjects(source: Type, target: Type) { const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source); const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target); if ( (globalStringType === source && stringType === target) || (globalNumberType === source && numberType === target) || (globalBooleanType === source && booleanType === target) || (getGlobalESSymbolType() === source && esSymbolType === target) ) { reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType); } } /** * Try and elaborate array and tuple errors. Returns false * if we have found an elaboration, or we should ignore * any other elaborations when relating the `source` and * `target` types. */ function tryElaborateArrayLikeErrors(source: Type, target: Type, reportErrors: boolean): boolean { /** * The spec for elaboration is: * - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. * - If the source is a tuple then skip property elaborations if the target is an array or tuple. * - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. * - If the source an array then skip property elaborations if the target is a tuple. */ if (isTupleType(source)) { if (source.target.readonly && isMutableArrayOrTuple(target)) { if (reportErrors) { reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); } return false; } return isArrayOrTupleType(target); } if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) { if (reportErrors) { reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); } return false; } if (isTupleType(target)) { return isArrayType(source); } return true; } function isRelatedToWorker(source: Type, target: Type, reportErrors?: boolean) { return isRelatedTo(source, target, RecursionFlags.Both, reportErrors); } /** * Compare two types and return * * Ternary.True if they are related with no assumptions, * * Ternary.Maybe if they are related with assumptions of other relationships, or * * Ternary.False if they are not related. */ function isRelatedTo(originalSource: Type, originalTarget: Type, recursionFlags: RecursionFlags = RecursionFlags.Both, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary { if (originalSource === originalTarget) return Ternary.True; // Before normalization: if `source` is type an object type, and `target` is primitive, // skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) { if ( relation === comparableRelation && !(originalTarget.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(originalTarget, originalSource, relation) || isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined) ) { return Ternary.True; } if (reportErrors) { reportErrorResults(originalSource, originalTarget, originalSource, originalTarget, headMessage); } return Ternary.False; } // Normalize the source and target types: Turn fresh literal types into regular literal types, // turn deferred type references into regular type references, simplify indexed access and // conditional types, and resolve substitution types to either the substitution (on the source // side) or the type variable (on the target side). const source = getNormalizedType(originalSource, /*writing*/ false); let target = getNormalizedType(originalTarget, /*writing*/ true); if (source === target) return Ternary.True; if (relation === identityRelation) { if (source.flags !== target.flags) return Ternary.False; if (source.flags & TypeFlags.Singleton) return Ternary.True; traceUnionsOrIntersectionsTooLarge(source, target); return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None, recursionFlags); } // We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common, // and otherwise, for type parameters in large unions, causes us to need to compare the union to itself, // as we break down the _target_ union first, _then_ get the source constraint - so for every // member of the target, we attempt to find a match in the source. This avoids that in cases where // the target is exactly the constraint. if (source.flags & TypeFlags.TypeParameter && getConstraintOfType(source) === target) { return Ternary.True; } // See if we're relating a definitely non-nullable type to a union that includes null and/or undefined // plus a single non-nullable type. If so, remove null and/or undefined from the target type. if (source.flags & TypeFlags.DefinitelyNonNullable && target.flags & TypeFlags.Union) { const types = (target as UnionType).types; const candidate = types.length === 2 && types[0].flags & TypeFlags.Nullable ? types[1] : types.length === 3 && types[0].flags & TypeFlags.Nullable && types[1].flags & TypeFlags.Nullable ? types[2] : undefined; if (candidate && !(candidate.flags & TypeFlags.Nullable)) { target = getNormalizedType(candidate, /*writing*/ true); if (source === target) return Ternary.True; } } if ( relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined) ) return Ternary.True; if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); if (isPerformingExcessPropertyChecks) { if (hasExcessProperties(source as FreshObjectLiteralType, target, reportErrors)) { if (reportErrors) { reportRelationError(headMessage, source, originalTarget.aliasSymbol ? originalTarget : target); } return Ternary.False; } } const isPerformingCommonPropertyChecks = (relation !== comparableRelation || isUnitType(source)) && !(intersectionState & IntersectionState.Target) && source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType && target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) && (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) { if (reportErrors) { const sourceString = typeToString(originalSource.aliasSymbol ? originalSource : source); const targetString = typeToString(originalTarget.aliasSymbol ? originalTarget : target); const calls = getSignaturesOfType(source, SignatureKind.Call); const constructs = getSignaturesOfType(source, SignatureKind.Construct); if ( calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, RecursionFlags.Source, /*reportErrors*/ false) || constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, RecursionFlags.Source, /*reportErrors*/ false) ) { reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, sourceString, targetString); } else { reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, sourceString, targetString); } } return Ternary.False; } traceUnionsOrIntersectionsTooLarge(source, target); const skipCaching = source.flags & TypeFlags.Union && (source as UnionType).types.length 0; if (maybeSuppress) { overrideNextErrorInfo--; } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { const currentError = errorInfo; tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ true); if (errorInfo !== currentError) { maybeSuppress = !!errorInfo; } } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { tryElaborateErrorsForPrimitivesAndObjects(source, target); } else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) { reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead); } else if (getObjectFlags(source) & ObjectFlags.JsxAttributes && target.flags & TypeFlags.Intersection) { const targetTypes = (target as IntersectionType).types; const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); if ( !isErrorType(intrinsicAttributes) && !isErrorType(intrinsicClassAttributes) && (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes)) ) { // do not report top error return; } } else { errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); } // Used by, eg, missing property checking to replace the top-level message with a more informative one. if (!headMessage && maybeSuppress) { // We suppress a call to `reportRelationError` or not depending on the state of the type checker, so // we call `reportRelationError` here and then undo its effects to figure out what would be the diagnostic // if we hadn't supress it, and save that as a canonical diagnostic for deduplication purposes. const savedErrorState = captureErrorCalculationState(); reportRelationError(headMessage, source, target); let canonical; if (errorInfo && errorInfo !== savedErrorState.errorInfo) { canonical = { code: errorInfo.code, messageText: errorInfo.messageText }; } resetErrorInfo(savedErrorState); if (canonical && errorInfo) { errorInfo.canonicalHead = canonical; } lastSkippedInfo = [source, target]; return; } reportRelationError(headMessage, source, target); if (source.flags & TypeFlags.TypeParameter && source.symbol?.declarations?.[0] && !getConstraintOfType(source as TypeVariable)) { const syntheticParam = cloneTypeParameter(source as TypeParameter); syntheticParam.constraint = instantiateType(target, makeUnaryTypeMapper(source, syntheticParam)); if (hasNonCircularBaseConstraint(syntheticParam)) { const targetConstraintString = typeToString(target, source.symbol.declarations[0]); associateRelatedInfo(createDiagnosticForNode(source.symbol.declarations[0], Diagnostics.This_type_parameter_might_need_an_extends_0_constraint, targetConstraintString)); } } } function traceUnionsOrIntersectionsTooLarge(source: Type, target: Type): void { if (!tracing) { return; } if ((source.flags & TypeFlags.UnionOrIntersection) && (target.flags & TypeFlags.UnionOrIntersection)) { const sourceUnionOrIntersection = source as UnionOrIntersectionType; const targetUnionOrIntersection = target as UnionOrIntersectionType; if (sourceUnionOrIntersection.objectFlags & targetUnionOrIntersection.objectFlags & ObjectFlags.PrimitiveUnion) { // There's a fast path for comparing primitive unions return; } const sourceSize = sourceUnionOrIntersection.types.length; const targetSize = targetUnionOrIntersection.types.length; if (sourceSize * targetSize > 1E6) { tracing.instant(tracing.Phase.CheckTypes, "traceUnionsOrIntersectionsTooLarge_DepthLimit", { sourceId: source.id, sourceSize, targetId: target.id, targetSize, pos: errorNode?.pos, end: errorNode?.end, }); } } } function getTypeOfPropertyInTypes(types: Type[], name: __String) { const appendPropType = (propTypes: Type[] | undefined, type: Type) => { type = getApparentType(type); const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name) : getPropertyOfObjectType(type, name); const propType = prop && getTypeOfSymbol(prop) || getApplicableIndexInfoForName(type, name)?.type || undefinedType; return append(propTypes, propType); }; return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray); } function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) { return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny } const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); if ( (relation === assignableRelation || relation === comparableRelation) && (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target))) ) { return false; } let reducedTarget = target; let checkTypes: Type[] | undefined; if (target.flags & TypeFlags.Union) { reducedTarget = findMatchingDiscriminantType(source, target as UnionType, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target as UnionType); checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget as UnionType).types : [reducedTarget]; } for (const prop of getPropertiesOfType(source)) { if (shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop)) { if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) { if (reportErrors) { // Report error in terms of object types in the target as those are the only ones // we check in isKnownProperty. const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget); // We know *exactly* where things went wrong when comparing the types. // Use this property as the error node as this will be more helpful in // reasoning about what went wrong. if (!errorNode) return Debug.fail(); if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) { // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. // However, using an object-literal error message will be very confusing to the users so we give different a message. if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) { // Note that extraneous children (as in `extra`) don't pass this check, // since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute. errorNode = prop.valueDeclaration.name; } const propName = symbolToString(prop); const suggestionSymbol = getSuggestedSymbolForNonexistentJSXAttribute(propName, errorTarget); const suggestion = suggestionSymbol ? symbolToString(suggestionSymbol) : undefined; if (suggestion) { reportError(Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, typeToString(errorTarget), suggestion); } else { reportError(Diagnostics.Property_0_does_not_exist_on_type_1, propName, typeToString(errorTarget)); } } else { // use the property's value declaration if the property is assigned inside the literal itself const objectLiteralDeclaration = source.symbol?.declarations && firstOrUndefined(source.symbol.declarations); let suggestion: string | undefined; if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) { const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike; Debug.assertNode(propDeclaration, isObjectLiteralElementLike); const name = propDeclaration.name!; errorNode = name; if (isIdentifier(name)) { suggestion = getSuggestionForNonexistentProperty(name, errorTarget); } } if (suggestion !== undefined) { reportParentSkippedError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, symbolToString(prop), typeToString(errorTarget), suggestion); } else { reportParentSkippedError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, symbolToString(prop), typeToString(errorTarget)); } } } return true; } if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), RecursionFlags.Both, reportErrors)) { if (reportErrors) { reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop)); } return true; } } } return false; } function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) { return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; } function unionOrIntersectionRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { // Note that these checks are specifically ordered to produce correct results. In particular, // we need to deconstruct unions before intersections (because unions are always at the top), // and we need to handle "each" relations before "some" relations for the same kind of type. if (source.flags & TypeFlags.Union) { if (target.flags & TypeFlags.Union) { // Intersections of union types are normalized into unions of intersection types, and such normalized // unions can get very large and expensive to relate. The following fast path checks if the source union // originated in an intersection. If so, and if that intersection contains the target type, then we know // the result to be true (for any two types A and B, A & B is related to both A and B). const sourceOrigin = (source as UnionType).origin; if (sourceOrigin && sourceOrigin.flags & TypeFlags.Intersection && target.aliasSymbol && contains((sourceOrigin as IntersectionType).types, target)) { return Ternary.True; } // Similarly, in unions of unions the we preserve the original list of unions. This original list is often // much shorter than the normalized result, so we scan it in the following fast path. const targetOrigin = (target as UnionType).origin; if (targetOrigin && targetOrigin.flags & TypeFlags.Union && source.aliasSymbol && contains((targetOrigin as UnionType).types, source)) { return Ternary.True; } } return relation === comparableRelation ? someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) : eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState); } if (target.flags & TypeFlags.Union) { return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive), intersectionState); } if (target.flags & TypeFlags.Intersection) { return typeRelatedToEachType(source, target as IntersectionType, reportErrors, IntersectionState.Target); } // Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the // constraints of all non-primitive types in the source into a new intersection. We do this because the // intersection may further constrain the constraints of the non-primitive types. For example, given a type // parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't // appear to be comparable to '2'. if (relation === comparableRelation && target.flags & TypeFlags.Primitive) { const constraints = sameMap((source as IntersectionType).types, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(t) || unknownType : t); if (constraints !== (source as IntersectionType).types) { source = getIntersectionType(constraints); if (source.flags & TypeFlags.Never) { return Ternary.False; } if (!(source.flags & TypeFlags.Intersection)) { return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false) || isRelatedTo(target, source, RecursionFlags.Source, /*reportErrors*/ false); } } } // Check to see if any constituents of the intersection are immediately related to the target. // Don't report errors though. Elaborating on whether a source constituent is related to the target is // not actually useful and leads to some confusing error messages. Instead, we rely on the caller // checking whether the full intersection viewed as an object is related to the target. return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source); } function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { let result = Ternary.True; const sourceTypes = source.types; for (const sourceType of sourceTypes) { const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false, IntersectionState.None); if (!related) { return Ternary.False; } result &= related; } return result; } function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const targetTypes = target.types; if (target.flags & TypeFlags.Union) { if (containsType(targetTypes, source)) { return Ternary.True; } if ( relation !== comparableRelation && getObjectFlags(target) & ObjectFlags.PrimitiveUnion && !(source.flags & TypeFlags.EnumLiteral) && ( source.flags & (TypeFlags.StringLiteral | TypeFlags.BooleanLiteral | TypeFlags.BigIntLiteral) || (relation === subtypeRelation || relation === strictSubtypeRelation) && source.flags & TypeFlags.NumberLiteral ) ) { // When relating a literal type to a union of primitive types, we know the relation is false unless // the union contains the base primitive type or the literal type in one of its fresh/regular forms. // We exclude numeric literals for non-subtype relations because numeric literals are assignable to // numeric enum literals with the same value. Similarly, we exclude enum literal types because // identically named enum types are related (see isEnumTypeRelatedTo). We exclude the comparable // relation in entirety because it needs to be checked in both directions. const alternateForm = source === (source as StringLiteralType).regularType ? (source as StringLiteralType).freshType : (source as StringLiteralType).regularType; const primitive = source.flags & TypeFlags.StringLiteral ? stringType : source.flags & TypeFlags.NumberLiteral ? numberType : source.flags & TypeFlags.BigIntLiteral ? bigintType : undefined; return primitive && containsType(targetTypes, primitive) || alternateForm && containsType(targetTypes, alternateForm) ? Ternary.True : Ternary.False; } const match = getMatchingUnionConstituentForType(target as UnionType, source); if (match) { const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); if (related) { return related; } } } for (const type of targetTypes) { const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); if (related) { return related; } } if (reportErrors) { // Elaborate only if we can find a best matching type in the target union const bestMatchingType = getBestMatchingType(source, target, isRelatedTo); if (bestMatchingType) { isRelatedTo(source, bestMatchingType, RecursionFlags.Target, /*reportErrors*/ true, /*headMessage*/ undefined, intersectionState); } } return Ternary.False; } function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { let result = Ternary.True; const targetTypes = target.types; for (const targetType of targetTypes) { const related = isRelatedTo(source, targetType, RecursionFlags.Target, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { return Ternary.False; } result &= related; } return result; } function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const sourceTypes = source.types; if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) { return Ternary.True; } const len = sourceTypes.length; for (let i = 0; i = (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 { propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable; return originalHandler!(onlyUnreliable); }; } let result: Ternary; if (expandingFlags === ExpandingFlags.Both) { tracing?.instant(tracing.Phase.CheckTypes, "recursiveTypeRelatedTo_DepthLimit", { sourceId: source.id, sourceIdStack: sourceStack.map(t => t.id), targetId: target.id, targetIdStack: targetStack.map(t => t.id), depth: sourceDepth, targetDepth, }); result = Ternary.Maybe; } else { tracing?.push(tracing.Phase.CheckTypes, "structuredTypeRelatedTo", { sourceId: source.id, targetId: target.id }); result = structuredTypeRelatedTo(source, target, reportErrors, intersectionState); tracing?.pop(); } if (outofbandVarianceMarkerHandler) { outofbandVarianceMarkerHandler = originalHandler; } if (recursionFlags & RecursionFlags.Source) { sourceDepth--; } if (recursionFlags & RecursionFlags.Target) { targetDepth--; } expandingFlags = saveExpandingFlags; if (result) { if (result === Ternary.True || (sourceDepth === 0 && targetDepth === 0)) { if (result === Ternary.True || result === Ternary.Maybe) { // If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe // results as having succeeded once we reach depth 0, but never record Ternary.Unknown results. resetMaybeStack(/*markAllAsSucceeded*/ true); } else { resetMaybeStack(/*markAllAsSucceeded*/ false); } } // Note: it's intentional that we don't reset in the else case; // we leave them on the stack such that when we hit depth zero // above, we can report all of them as successful. } else { // A false result goes straight into global cache (when something is false under // assumptions it will also be false without assumptions) relation.set(id, RelationComparisonResult.Failed | propagatingVarianceFlags); relationCount--; resetMaybeStack(/*markAllAsSucceeded*/ false); } return result; function resetMaybeStack(markAllAsSucceeded: boolean) { for (let i = maybeStart; i c !== source)) { // Skip comparison if expansion contains the source itself // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); } } // When the target is an intersection we need an extra property check in order to detect nested excess // properties and nested weak types. The following are motivating examples that all should be errors, but // aren't without this extra property check: // // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property // // declare let wrong: { a: { y: string } }; // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type // if ( result && !(intersectionState & IntersectionState.Target) && target.flags & TypeFlags.Intersection && !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection) ) { result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, IntersectionState.None); if (result && isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) { result &= indexSignaturesRelatedTo(source, target, /*sourceIsPrimitive*/ false, reportErrors, IntersectionState.None); } } // When the source is an intersection we need an extra check of any optional properties in the target to // detect possible mismatched property types. For example: // // function foo(x: { a?: string }, y: T & { a: boolean }) { // x = y; // Mismatched property in source intersection // } // else if ( result && isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => t === target || !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)) ) { result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ true, intersectionState); } } if (result) { resetErrorInfo(saveErrorInfo); } return result; } function getApparentMappedTypeKeys(nameType: Type, targetType: MappedType) { const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType)); const mappedKeys: Type[] = []; forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType( modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, /*stringsOnly*/ false, t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))), ); return getUnionType(mappedKeys); } function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, saveErrorInfo: ReturnType): Ternary { let result: Ternary; let originalErrorInfo: DiagnosticMessageChain | undefined; let varianceCheckFailed = false; let sourceFlags = source.flags; const targetFlags = target.flags; if (relation === identityRelation) { // We've already checked that source.flags and target.flags are identical if (sourceFlags & TypeFlags.UnionOrIntersection) { let result = eachTypeRelatedToSomeType(source as UnionOrIntersectionType, target as UnionOrIntersectionType); if (result) { result &= eachTypeRelatedToSomeType(target as UnionOrIntersectionType, source as UnionOrIntersectionType); } return result; } if (sourceFlags & TypeFlags.Index) { return isRelatedTo((source as IndexType).type, (target as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false); } if (sourceFlags & TypeFlags.IndexedAccess) { if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, /*reportErrors*/ false)) { if (result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, /*reportErrors*/ false)) { return result; } } } if (sourceFlags & TypeFlags.Conditional) { if ((source as ConditionalType).root.isDistributive === (target as ConditionalType).root.isDistributive) { if (result = isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both, /*reportErrors*/ false)) { if (result &= isRelatedTo((source as ConditionalType).extendsType, (target as ConditionalType).extendsType, RecursionFlags.Both, /*reportErrors*/ false)) { if (result &= isRelatedTo(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { if (result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { return result; } } } } } } if (sourceFlags & TypeFlags.Substitution) { if (result = isRelatedTo((source as SubstitutionType).baseType, (target as SubstitutionType).baseType, RecursionFlags.Both, /*reportErrors*/ false)) { if (result &= isRelatedTo((source as SubstitutionType).constraint, (target as SubstitutionType).constraint, RecursionFlags.Both, /*reportErrors*/ false)) { return result; } } } if (sourceFlags & TypeFlags.TemplateLiteral) { if (arrayIsEqualTo((source as TemplateLiteralType).texts, (target as TemplateLiteralType).texts)) { const sourceTypes = (source as TemplateLiteralType).types; const targetTypes = (target as TemplateLiteralType).types; result = Ternary.True; for (let i = 0; i { a, b: true } | { a, b: false }). // Source is an intersection, target is an object (e.g. { a } & { b } { a, b }). // Source is an intersection, target is a union (e.g. { a } & { b: boolean } { a, b: true } | { a, b: false }). // Source is an intersection, target instantiable (e.g. string & { tag } T["a"] constrained to string & { tag }). if ( !(sourceFlags & TypeFlags.Instantiable || sourceFlags & TypeFlags.Object && targetFlags & TypeFlags.Union || sourceFlags & TypeFlags.Intersection && targetFlags & (TypeFlags.Object | TypeFlags.Union | TypeFlags.Instantiable)) ) { return Ternary.False; } } // We limit alias variance probing to only object and conditional types since their alias behavior // is more predictable than other, interned types, which may or may not have an alias depending on // the order in which things were checked. if ( sourceFlags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol && !(isMarkerType(source) || isMarkerType(target)) ) { const variances = getAliasVariances(source.aliasSymbol); if (variances === emptyArray) { return Ternary.Unknown; } const params = getSymbolLinks(source.aliasSymbol).typeParameters!; const minParams = getMinTypeArgumentCount(params); const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); const varianceResult = relateVariances(sourceTypes, targetTypes, variances, intersectionState); if (varianceResult !== undefined) { return varianceResult; } } // For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T], // and U is assignable to [...T] when U is constrained to a mutable array or tuple type. if ( isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target, RecursionFlags.Source)) || isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0], RecursionFlags.Target)) ) { return result; } if (targetFlags & TypeFlags.TypeParameter) { // A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q]. if (getObjectFlags(source) & ObjectFlags.Mapped && !(source as MappedType).declaration.nameType && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(source as MappedType), RecursionFlags.Both)) { if (!(getMappedTypeModifiers(source as MappedType) & MappedTypeModifiers.IncludeOptional)) { const templateType = getTemplateTypeFromMappedType(source as MappedType); const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source as MappedType)); if (result = isRelatedTo(templateType, indexedAccessType, RecursionFlags.Both, reportErrors)) { return result; } } } if (relation === comparableRelation && sourceFlags & TypeFlags.TypeParameter) { // This is a carve-out in comparability to essentially forbid comparing a type parameter // with another type parameter unless one extends the other. (Remember: comparability is mostly bidirectional!) let constraint = getConstraintOfTypeParameter(source); if (constraint) { while (constraint && someType(constraint, c => !!(c.flags & TypeFlags.TypeParameter))) { if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false)) { return result; } constraint = getConstraintOfTypeParameter(constraint); } } return Ternary.False; } } else if (targetFlags & TypeFlags.Index) { const targetType = (target as IndexType).type; // A keyof S is related to a keyof T if T is related to S. if (sourceFlags & TypeFlags.Index) { if (result = isRelatedTo(targetType, (source as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false)) { return result; } } if (isTupleType(targetType)) { // An index type can have a tuple type target when the tuple type contains variadic elements. // Check if the source is related to the known keys of the tuple type. if (result = isRelatedTo(source, getKnownKeysOfTupleType(targetType), RecursionFlags.Target, reportErrors)) { return result; } } else { // A type S is assignable to keyof T if S is assignable to keyof C, where C is the // simplified form of T or, if T doesn't simplify, the constraint of T. const constraint = getSimplifiedTypeOrConstraint(targetType); if (constraint) { // We require Ternary.True here such that circular constraints don't cause // false positives. For example, given 'T extends { [K in keyof T]: string }', // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when // related to other types. if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).indexFlags | IndexFlags.NoReducibleCheck), RecursionFlags.Target, reportErrors) === Ternary.True) { return Ternary.True; } } else if (isGenericMappedType(targetType)) { // generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against // - their nameType or constraintType. // In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types const nameType = getNameTypeFromMappedType(targetType); const constraintType = getConstraintTypeFromMappedType(targetType); let targetKeys; if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) { // we need to get the apparent mappings and union them with the generic mappings, since some properties may be // missing from the `constraintType` which will otherwise be mapped in the object const mappedKeys = getApparentMappedTypeKeys(nameType, targetType); // We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side) targetKeys = getUnionType([mappedKeys, nameType]); } else { targetKeys = nameType || constraintType; } if (isRelatedTo(source, targetKeys, RecursionFlags.Target, reportErrors) === Ternary.True) { return Ternary.True; } } } } else if (targetFlags & TypeFlags.IndexedAccess) { if (sourceFlags & TypeFlags.IndexedAccess) { // Relate components directly before falling back to constraint relationships // A type S[K] is related to a type T[J] if S is related to T and K is related to J. if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, reportErrors)) { result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, reportErrors); } if (result) { return result; } if (reportErrors) { originalErrorInfo = errorInfo; } } // A type S is related to a type T[K] if S is related to C, where C is the base // constraint of T[K] for writing. if (relation === assignableRelation || relation === comparableRelation) { const objectType = (target as IndexedAccessType).objectType; const indexType = (target as IndexedAccessType).indexType; const baseObjectType = getBaseConstraintOfType(objectType) || objectType; const baseIndexType = getBaseConstraintOfType(indexType) || indexType; if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) { const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0); const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, accessFlags); if (constraint) { if (reportErrors && originalErrorInfo) { // create a new chain for the constraint error resetErrorInfo(saveErrorInfo); } if (result = isRelatedTo(source, constraint, RecursionFlags.Target, reportErrors, /*headMessage*/ undefined, intersectionState)) { return result; } // prefer the shorter chain of the constraint comparison chain, and the direct comparison chain if (reportErrors && originalErrorInfo && errorInfo) { errorInfo = countMessageChainBreadth([originalErrorInfo]) isTupleType(t) && !t.target.readonly)) { if (relation !== identityRelation) { return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, reportErrors); } else { // By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple // or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction return Ternary.False; } } else if (isGenericTupleType(source) && isTupleType(target) && !isGenericTupleType(target)) { const constraint = getBaseConstraintOrType(source); if (constraint !== source) { return isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors); } } // A fresh empty object type is never a subtype of a non-empty object type. This ensures fresh({}) <: string xxx but not vice-versa. without this rule those types would be mutual subtypes. else if subtyperelation relation="==" strictsubtyperelation isemptyobjecttype getobjectflags objectflags.freshliteral return ternary.false even relationship doesn hold for unions intersections or generic type references it may in a structural comparison. check of the form x="A" b we will have previously checked relates to x. failing both want aggregation and members structurally thus include intersection on source side here. typeflags.intersection targetflags typeflags.object report errors only haven reported any yet const reportstructuralerrors="reportErrors" errorinfo="==" saveerrorinfo.errorinfo result="propertiesRelatedTo(source," target undefined false intersectionstate signaturesrelatedto signaturekind.call signaturekind.construct indexsignaturesrelatedto sourceisprimitive use variance error is no one s an object t discriminated union related there exists constituent every combination discriminants with respect t. do here as existing from checking each union. typeflags.union objectonlytarget="extractTypesOfKind(target," typeflags.substitution uniontype function countmessagechainbreadth diagnosticmessagechain number reduceleft chain> value + 1 + countMessageChainBreadth(chain.next), 0); } function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], intersectionState: IntersectionState) { if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState)) { return result; } if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) { // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we // have to allow a structural fallback check // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially // be assuming identity of the type parameter. originalErrorInfo = undefined; resetErrorInfo(saveErrorInfo); return undefined; } const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances); varianceCheckFailed = !allowStructuralFallback; // The type arguments did not relate appropriately, but it may be because we have no variance // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type // arguments). It might also be the case that the target type has a 'void' type argument for // a covariant type parameter that is only used in return positions within the generic type // (in which case any type argument is permitted on the source side). In those cases we proceed // with a structural comparison. Otherwise, we know for certain the instantiations aren't // related and we can return here. if (variances !== emptyArray && !allowStructuralFallback) { // In some cases generic types that are covariant in regular type checking mode become // invariant in --strictFunctionTypes mode because one or more type parameters are used in // both co- and contravariant positions. In order to make it easier to diagnose *why* such // types are invariant, if any of the type parameters are invariant we reset the reported // errors and instead force a structural comparison (which will include elaborations that // reveal the reason). // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, // we can return `False` early here to skip calculating the structural error message we don't need. if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) { return Ternary.False; } // We remember the original error information so we can restore it in case the structural // comparison unexpectedly succeeds. This can happen when the structural comparison result // is a Ternary.Maybe for example caused by the recursion depth limiter. originalErrorInfo = errorInfo; resetErrorInfo(saveErrorInfo); } } } // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice // that S and T are contra-variant whereas X and Y are co-variant. function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary { const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) : getCombinedMappedTypeOptionality(source) 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 combination[i], /*reportErrors*/ false, IntersectionState.None, /*skipOptional*/ strictNullChecks || relation === comparableRelation); // If the target property could not be found, or if the properties were not related, // then this constituent is not a match. if (!related) { continue outer; } } pushIfUnique(matchingTypes, type, equateValues); hasMatch = true; } if (!hasMatch) { // We failed to match any type for this combination. return Ternary.False; } } // Compare the remaining non-discriminant properties of each match. let result = Ternary.True; for (const type of matchingTypes) { result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, /*optionalsOnly*/ false, IntersectionState.None); if (result) { result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportErrors*/ false, IntersectionState.None); if (result) { result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportErrors*/ false, IntersectionState.None); if (result && !(isTupleType(source) && isTupleType(type))) { // Comparing numeric index types when both `source` and `type` are tuples is unnecessary as the // element types should be sufficiently covered by `propertiesRelatedTo`. It also causes problems // with index type assignability as the types for the excluded discriminants are still included // in the index type. result &= indexSignaturesRelatedTo(source, type, /*sourceIsPrimitive*/ false, /*reportErrors*/ false, IntersectionState.None); } } } if (!result) { return result; } } return result; } function excludeProperties(properties: Symbol[], excludedProperties: Set<__string> | undefined) { if (!excludedProperties || properties.length === 0) return properties; let result: Symbol[] | undefined; for (let i = 0; i Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); const effectiveTarget = addOptionality(getNonMissingTypeOfSymbol(targetProp), /*isProperty*/ false, targetIsOptional); // source could resolve to `any` and that's not related to `unknown` target under strict subtype relation if (effectiveTarget.flags & (relation === strictSubtypeRelation ? TypeFlags.Any : TypeFlags.AnyOrUnknown)) { return Ternary.True; } const effectiveSource = getTypeOfSourceProperty(sourceProp); return isRelatedTo(effectiveSource, effectiveTarget, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); } function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState, skipOptional: boolean): Ternary { const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) { if (reportErrors) { if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); } else { reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); } } return Ternary.False; } } else if (targetPropFlags & ModifierFlags.Protected) { if (!isValidOverrideOf(sourceProp, targetProp)) { if (reportErrors) { reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); } return Ternary.False; } } else if (sourcePropFlags & ModifierFlags.Protected) { if (reportErrors) { reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, symbolToString(targetProp), typeToString(source), typeToString(target)); } return Ternary.False; } // Ensure {readonly a: whatever} is not a subtype of {a: whatever}, // while {a: whatever} is a subtype of {readonly a: whatever}. // This ensures the subtype relationship is ordered, and preventing declaration order // from deciding which type "wins" in union subtype reduction. // They're still assignable to one another, since `readonly` doesn't affect assignability. // This is only applied during the strictSubtypeRelation -- currently used in subtype reduction if ( relation === strictSubtypeRelation && isReadonlySymbol(sourceProp) && !isReadonlySymbol(targetProp) ) { return Ternary.False; } // If the target comes from a partial union prop, allow `undefined` in the target type const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState); if (!related) { if (reportErrors) { reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); } return Ternary.False; } // When checking for comparability, be more lenient with optional properties. if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && targetProp.flags & SymbolFlags.ClassMember && !(targetProp.flags & SymbolFlags.Optional)) { // TypeScript 1.0 spec (April 2014): 3.8.3 // S is a subtype of a type T, and T is a supertype of S if ... // S' and T are object types and, for each member M in T.. // M is a property and S' contains a property N where // if M is a required property, N is also a required property // (M - property in T) // (N - property in S) if (reportErrors) { reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, symbolToString(targetProp), typeToString(source), typeToString(target)); } return Ternary.False; } return related; } function reportUnmatchedProperty(source: Type, target: Type, unmatchedProperty: Symbol, requireOptionalProperties: boolean) { let shouldSkipElaboration = false; // give specific error in case where private names have the same description if ( unmatchedProperty.valueDeclaration && isNamedDeclaration(unmatchedProperty.valueDeclaration) && isPrivateIdentifier(unmatchedProperty.valueDeclaration.name) && source.symbol && source.symbol.flags & SymbolFlags.Class ) { const privateIdentifierDescription = unmatchedProperty.valueDeclaration.name.escapedText; const symbolTableKey = getSymbolNameForPrivateIdentifier(source.symbol, privateIdentifierDescription); if (symbolTableKey && getPropertyOfType(source, symbolTableKey)) { const sourceName = factory.getDeclarationName(source.symbol.valueDeclaration); const targetName = factory.getDeclarationName(target.symbol.valueDeclaration); reportError( Diagnostics.Property_0_in_type_1_refers_to_a_different_member_that_cannot_be_accessed_from_within_type_2, diagnosticName(privateIdentifierDescription), diagnosticName(sourceName.escapedText === "" ? anon : sourceName), diagnosticName(targetName.escapedText === "" ? anon : targetName), ); return; } } const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false)); if ( !headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code && headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code) ) { shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it } if (props.length === 1) { const propName = symbolToString(unmatchedProperty, /*enclosingDeclaration*/ undefined, SymbolFlags.None, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteComputedProps); reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); if (length(unmatchedProperty.declarations)) { associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName)); } if (shouldSkipElaboration && errorInfo) { overrideNextErrorInfo++; } } else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) { if (props.length > 5) { // arbitrary cutoff for too-long list form reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4); } else { reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, typeToString(source), typeToString(target), map(props, p => symbolToString(p)).join(", ")); } if (shouldSkipElaboration && errorInfo) { overrideNextErrorInfo++; } } // No array like or unmatched property error - just issue top level error (errorInfo = undefined) } function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__string> | undefined, optionalsOnly: boolean, intersectionState: IntersectionState): Ternary { if (relation === identityRelation) { return propertiesIdenticalTo(source, target, excludedProperties); } let result = Ternary.True; if (isTupleType(target)) { if (isArrayOrTupleType(source)) { if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) { return Ternary.False; } const sourceArity = getTypeReferenceArity(source); const targetArity = getTypeReferenceArity(target); const sourceRestFlag = isTupleType(source) ? source.target.combinedFlags & ElementFlags.Rest : ElementFlags.Rest; const targetHasRestElement = !!(target.target.combinedFlags & ElementFlags.Variable); const sourceMinLength = isTupleType(source) ? source.target.minLength : 0; const targetMinLength = target.target.minLength; if (!sourceRestFlag && sourceArity = targetStartCount ? targetArity - 1 - Math.min(sourcePositionFromEnd, targetEndCount) : sourcePosition; const targetFlags = target.target.elementFlags[targetPosition]; if (targetFlags & ElementFlags.Variadic && !(sourceFlags & ElementFlags.Variadic)) { if (reportErrors) { reportError(Diagnostics.Source_provides_no_match_for_variadic_element_at_position_0_in_target, targetPosition); } return Ternary.False; } if (sourceFlags & ElementFlags.Variadic && !(targetFlags & ElementFlags.Variable)) { if (reportErrors) { reportError(Diagnostics.Variadic_element_at_position_0_in_source_does_not_match_element_at_position_1_in_target, sourcePosition, targetPosition); } return Ternary.False; } if (targetFlags & ElementFlags.Required && !(sourceFlags & ElementFlags.Required)) { if (reportErrors) { reportError(Diagnostics.Source_provides_no_match_for_required_element_at_position_0_in_target, targetPosition); } return Ternary.False; } // We can only exclude discriminant properties if we have not yet encountered a variable-length element. if (canExcludeDiscriminants) { if (sourceFlags & ElementFlags.Variable || targetFlags & ElementFlags.Variable) { canExcludeDiscriminants = false; } if (canExcludeDiscriminants && excludedProperties?.has(("" + sourcePosition) as __String)) { continue; } } const sourceType = removeMissingType(sourceTypeArguments[sourcePosition], !!(sourceFlags & targetFlags & ElementFlags.Optional)); const targetType = targetTypeArguments[targetPosition]; const targetCheckType = sourceFlags & ElementFlags.Variadic && targetFlags & ElementFlags.Rest ? createArrayType(targetType) : removeMissingType(targetType, !!(targetFlags & ElementFlags.Optional)); const related = isRelatedTo(sourceType, targetCheckType, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { if (reportErrors && (targetArity > 1 || sourceArity > 1)) { if (targetHasRestElement && sourcePosition >= targetStartCount && sourcePositionFromEnd >= targetEndCount && targetStartCount !== sourceArity - targetEndCount - 1) { reportIncompatibleError(Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, targetStartCount, sourceArity - targetEndCount - 1, targetPosition); } else { reportIncompatibleError(Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, sourcePosition, targetPosition); } } return Ternary.False; } result &= related; } return result; } if (target.target.combinedFlags & ElementFlags.Variable) { return Ternary.False; } } const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); if (unmatchedProperty) { if (reportErrors && shouldReportUnmatchedPropertyError(source, target)) { reportUnmatchedProperty(source, target, unmatchedProperty, requireOptionalProperties); } return Ternary.False; } if (isObjectLiteralType(target)) { for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { const sourceType = getTypeOfSymbol(sourceProp); if (!(sourceType.flags & TypeFlags.Undefined)) { if (reportErrors) { reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); } return Ternary.False; } } } } // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ // from the target union, across all members const properties = getPropertiesOfType(target); const numericNamesOnly = isTupleType(source) && isTupleType(target); for (const targetProp of excludeProperties(properties, excludedProperties)) { const name = targetProp.escapedName; if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length") && (!optionalsOnly || targetProp.flags & SymbolFlags.Optional)) { const sourceProp = getPropertyOfType(source, name); if (sourceProp && sourceProp !== targetProp) { const related = propertyRelatedTo(source, target, sourceProp, targetProp, getNonMissingTypeOfSymbol, reportErrors, intersectionState, relation === comparableRelation); if (!related) { return Ternary.False; } result &= related; } } } return result; } function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: Set<__string> | undefined): Ternary { if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { return Ternary.False; } const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties); const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties); if (sourceProperties.length !== targetProperties.length) { return Ternary.False; } let result = Ternary.True; for (const sourceProp of sourceProperties) { const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName); if (!targetProp) { return Ternary.False; } const related = compareProperties(sourceProp, targetProp, isRelatedTo); if (!related) { return Ternary.False; } result &= related; } return result; } function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (relation === identityRelation) { return signaturesIdenticalTo(source, target, kind); } if (target === anyFunctionType || source === anyFunctionType) { return Ternary.True; } const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration); const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration); const sourceSignatures = getSignaturesOfType( source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ? SignatureKind.Call : kind, ); const targetSignatures = getSignaturesOfType( target, (targetIsJSConstructor && kind === SignatureKind.Construct) ? SignatureKind.Call : kind, ); if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) { const sourceIsAbstract = !!(sourceSignatures[0].flags & SignatureFlags.Abstract); const targetIsAbstract = !!(targetSignatures[0].flags & SignatureFlags.Abstract); if (sourceIsAbstract && !targetIsAbstract) { // An abstract constructor type is not assignable to a non-abstract constructor type // as it would otherwise be possible to new an abstract class. Note that the assignability // check we perform for an extends clause excludes construct signatures from the target, // so this check never proceeds. if (reportErrors) { reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); } return Ternary.False; } if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) { return Ternary.False; } } let result = Ternary.True; const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn; const sourceObjectFlags = getObjectFlags(source); const targetObjectFlags = getObjectFlags(target); if ( sourceObjectFlags & ObjectFlags.Instantiated && targetObjectFlags & ObjectFlags.Instantiated && source.symbol === target.symbol || sourceObjectFlags & ObjectFlags.Reference && targetObjectFlags & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target ) { // We have instantiations of the same anonymous type (which typically will be the type of a // method). Simply do a pairwise comparison of the signatures in the two signature lists instead // of the much more expensive N * M comparison matrix we explore below. We erase type parameters // as they are known to always be the same. Debug.assertEqual(sourceSignatures.length, targetSignatures.length); for (let i = 0; i signatureToString(signature, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrowStyleSignature, kind); reportError(Diagnostics.Type_0_is_not_assignable_to_type_1, constructSignatureToString(sourceSignature), constructSignatureToString(targetSignature)); reportError(Diagnostics.Types_of_construct_signatures_are_incompatible); return result; } } else { outer: for (const t of targetSignatures) { const saveErrorInfo = captureErrorCalculationState(); // Only elaborate errors from the first failure let shouldElaborateErrors = reportErrors; for (const s of sourceSignatures) { const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, intersectionState, incompatibleReporter(s, t)); if (related) { result &= related; resetErrorInfo(saveErrorInfo); continue outer; } shouldElaborateErrors = false; } if (shouldElaborateErrors) { reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, typeToString(source), signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind)); } return Ternary.False; } } return result; } function shouldReportUnmatchedPropertyError(source: Type, target: Type): boolean { const typeCallSignatures = getSignaturesOfStructuredType(source, SignatureKind.Call); const typeConstructSignatures = getSignaturesOfStructuredType(source, SignatureKind.Construct); const typeProperties = getPropertiesOfObjectType(source); if ((typeCallSignatures.length || typeConstructSignatures.length) && !typeProperties.length) { if ( (getSignaturesOfType(target, SignatureKind.Call).length && typeCallSignatures.length) || (getSignaturesOfType(target, SignatureKind.Construct).length && typeConstructSignatures.length) ) { return true; // target has similar signature kinds to source, still focus on the unmatched property } return false; } return true; } function reportIncompatibleCallSignatureReturn(siga: Signature, sigb: Signature) { if (siga.parameters.length === 0 && sigb.parameters.length === 0) { return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); } function reportIncompatibleConstructSignatureReturn(siga: Signature, sigb: Signature) { if (siga.parameters.length === 0 && sigb.parameters.length === 0) { return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); } /** * See signatureAssignableTo, compareSignaturesIdentical */ function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, intersectionState: IntersectionState, incompatibleReporter: (source: Type, target: Type) => void): Ternary { const checkMode = relation === subtypeRelation ? SignatureCheckMode.StrictTopSignature : relation === strictSubtypeRelation ? SignatureCheckMode.StrictTopSignature | SignatureCheckMode.StrictArity : SignatureCheckMode.None; return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, checkMode, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); function isRelatedToWorker(source: Type, target: Type, reportErrors?: boolean) { return isRelatedTo(source, target, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); } } function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { const sourceSignatures = getSignaturesOfType(source, kind); const targetSignatures = getSignaturesOfType(target, kind); if (sourceSignatures.length !== targetSignatures.length) { return Ternary.False; } let result = Ternary.True; for (let i = 0; i info.keyType === stringType); let result = Ternary.True; for (const targetInfo of indexInfos) { const related = relation !== strictSubtypeRelation && !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True : isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, RecursionFlags.Both, reportErrors) : typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); if (!related) { return Ternary.False; } result &= related; } return result; } function typeRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); if (sourceInfo) { return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors, intersectionState); } // Intersection constituents are never considered to have an inferred index signature. Also, in the strict subtype relation, // only fresh object literals are considered to have inferred index signatures. This ensures { [x: string]: xxx } <: but not vice-versa. without this rule those types would be mutual strict subtypes. if intersectionstate.source strictsubtyperelation getobjectflags objectflags.freshliteral isobjecttypewithinferableindex return membersrelatedtoindexinfo targetinfo reporterrors intersectionstate reporterror typetostring ternary.false function indexsignaturesidenticalto type target: ternary const sourceinfos="getIndexInfosOfType(source);" targetinfos="getIndexInfosOfType(target);" targetinfos.length for of sourceinfo="getIndexInfoOfType(source," targetinfo.keytype isrelatedto targetinfo.type recursionflags.both sourceinfo.isreadonly="==" targetinfo.isreadonly ternary.true constructorvisibilitiesarecompatible signature targetsignature: reporterrors: boolean true sourceaccessibility="getSelectedEffectiveModifierFlags(sourceSignature.declaration," modifierflags.nonpublicaccessibilitymodifier targetaccessibility="getSelectedEffectiveModifierFlags(targetSignature.declaration," a public protected and private is assignable to signature. modifierflags.private modifierflags.protected only visibilitytostring false typecouldhavetoplevelsingletontypes okay yes union that useful in error reporting scenarios. you need use detail matters feel free add flag. typeflags.boolean typeflags.unionorintersection as intersectiontype typeflags.instantiable constraint="getConstraintOfType(type);" isunittype typeflags.templateliteral typeflags.stringmapping getexactoptionalunassignableproperties istupletype emptyarray getpropertiesoftype .filter> isExactOptionalPropertyMismatch(getTypeOfPropertyOfType(source, targetProp.escapedName), getTypeOfSymbol(targetProp))); } function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) { return !!source && !!target && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target); } function getExactOptionalProperties(type: Type) { return getPropertiesOfType(type).filter(targetProp => containsMissingType(getTypeOfSymbol(targetProp))); } function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { return findMatchingDiscriminantType(source, target, isRelatedTo) || findMatchingTypeReferenceOrTypeAliasReference(source, target) || findBestTypeForObjectLiteral(source, target) || findBestTypeForInvokable(source, target) || findMostOverlappyType(source, target); } function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: (readonly [() => Type, __String])[], related: (source: Type, target: Type) => boolean | Ternary) { const types = target.types; const include: Ternary[] = types.map(t => t.flags & TypeFlags.Primitive ? Ternary.False : Ternary.True); for (const [getDiscriminatingType, propertyName] of discriminators) { // If the remaining target types include at least one with a matching discriminant, eliminate those that // have non-matching discriminants. This ensures that we ignore erroneous discriminators and gradually // refine the target set without eliminating every constituent (which would lead to `never`). let matched = false; for (let i = 0; i !!related(t, targetType))) { matched = true; } else { include[i] = Ternary.Maybe; } } } } // Turn each Ternary.Maybe into Ternary.False if there was a match. Otherwise, revert to Ternary.True. for (let i = 0; i include[i]), UnionReduction.None) : target; return filtered.flags & TypeFlags.Never ? target : filtered; } /** * A type is 'weak' if it is an object type with at least one optional property * and no required properties, call/construct signatures or index signatures */ function isWeakType(type: Type): boolean { if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type as ObjectType); return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && resolved.indexInfos.length === 0 && resolved.properties.length > 0 && every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional)); } if (type.flags & TypeFlags.Substitution) { return isWeakType((type as SubstitutionType).baseType); } if (type.flags & TypeFlags.Intersection) { return every((type as IntersectionType).types, isWeakType); } return false; } function hasCommonProperties(source: Type, target: Type, isComparingJsxAttributes: boolean) { for (const prop of getPropertiesOfType(source)) { if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) { return true; } } return false; } function getVariances(type: GenericType): VarianceFlags[] { // Arrays and tuples are known to be covariant, no need to spend time computing this. return type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple ? arrayVariances : getVariancesWorker(type.symbol, type.typeParameters); } function getAliasVariances(symbol: Symbol) { return getVariancesWorker(symbol, getSymbolLinks(symbol).typeParameters); } // Return an array containing the variance of each type parameter. The variance is effectively // a digest of the type comparisons that occur for each type argument when instantiations of the // generic type are structurally compared. We infer the variance information by comparing // instantiations of the generic type for type arguments with known relations. The function // returns the emptyArray singleton when invoked recursively for the given generic type. function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray): VarianceFlags[] { const links = getSymbolLinks(symbol); if (!links.variances) { tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getTypeId(getDeclaredTypeOfSymbol(symbol)) }); const oldVarianceComputation = inVarianceComputation; const saveResolutionStart = resolutionStart; if (!inVarianceComputation) { inVarianceComputation = true; resolutionStart = resolutionTargets.length; } links.variances = emptyArray; const variances = []; for (const tp of typeParameters) { const modifiers = getTypeParameterModifiers(tp); let variance = modifiers & ModifierFlags.Out ? modifiers & ModifierFlags.In ? VarianceFlags.Invariant : VarianceFlags.Covariant : modifiers & ModifierFlags.In ? VarianceFlags.Contravariant : undefined; if (variance === undefined) { let unmeasurable = false; let unreliable = false; const oldHandler = outofbandVarianceMarkerHandler; outofbandVarianceMarkerHandler = onlyUnreliable => onlyUnreliable ? unreliable = true : unmeasurable = true; // We first compare instantiations where the type parameter is replaced with // marker types that have a known subtype relationship. From this we can infer // invariance, covariance, contravariance or bivariance. const typeWithSuper = createMarkerType(symbol, tp, markerSuperType); const typeWithSub = createMarkerType(symbol, tp, markerSubType); variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) | (isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0); // If the instantiations appear to be related bivariantly it may be because the // type parameter is independent (i.e. it isn't witnessed anywhere in the generic // type). To determine this we compare instantiations where the type parameter is // replaced with marker types that are known to be unrelated. if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(symbol, tp, markerOtherType), typeWithSuper)) { variance = VarianceFlags.Independent; } outofbandVarianceMarkerHandler = oldHandler; if (unmeasurable || unreliable) { if (unmeasurable) { variance |= VarianceFlags.Unmeasurable; } if (unreliable) { variance |= VarianceFlags.Unreliable; } } } variances.push(variance); } if (!oldVarianceComputation) { inVarianceComputation = false; resolutionStart = saveResolutionStart; } links.variances = variances; tracing?.pop({ variances: variances.map(Debug.formatVariance) }); } return links.variances; } function createMarkerType(symbol: Symbol, source: TypeParameter, target: Type) { const mapper = makeUnaryTypeMapper(source, target); const type = getDeclaredTypeOfSymbol(symbol); if (isErrorType(type)) { return type; } const result = symbol.flags & SymbolFlags.TypeAlias ? getTypeAliasInstantiation(symbol, instantiateTypes(getSymbolLinks(symbol).typeParameters!, mapper)) : createTypeReference(type as GenericType, instantiateTypes((type as GenericType).typeParameters, mapper)); markerTypes.add(getTypeId(result)); return result; } function isMarkerType(type: Type) { return markerTypes.has(getTypeId(type)); } function getTypeParameterModifiers(tp: TypeParameter): ModifierFlags { return reduceLeft(tp.symbol?.declarations, (modifiers, d) => modifiers | getEffectiveModifierFlags(d), ModifierFlags.None) & (ModifierFlags.In | ModifierFlags.Out | ModifierFlags.Const); } // Return true if the given type reference has a 'void' type argument for a covariant type parameter. // See comment at call in recursiveTypeRelatedTo for when this case matters. function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean { for (let i = 0; i !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t)); } function getGenericTypeReferenceRelationKey(source: TypeReference, target: TypeReference, postFix: string, ignoreConstraints: boolean) { const typeParameters: Type[] = []; let constraintMarker = ""; const sourceId = getTypeReferenceId(source, 0); const targetId = getTypeReferenceId(target, 0); return `${constraintMarker}${sourceId},${targetId}${postFix}`; // getTypeReferenceId(A) returns "111=0-12=1" // where A.id=111 and number.id=12 function getTypeReferenceId(type: TypeReference, depth = 0) { let result = "" + type.target.id; for (const t of getTypeArguments(type)) { if (t.flags & TypeFlags.TypeParameter) { if (ignoreConstraints || isUnconstrainedTypeParameter(t)) { let index = typeParameters.indexOf(t); if (index "; continue; } result += "-" + t.id; } return result; } } /** * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. * For other cases, the types ids are used. */ function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: Map, ignoreConstraints: boolean) { if (relation === identityRelation && source.id > target.id) { const temp = source; source = target; target = temp; } const postFix = intersectionState ? ":" + intersectionState : ""; return isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) ? getGenericTypeReferenceRelationKey(source as TypeReference, target as TypeReference, postFix, ignoreConstraints) : `${source.id},${target.id}${postFix}`; } // Invoke the callback for each underlying property symbol of the given symbol and return the first // value that isn't undefined. function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T | undefined { if (getCheckFlags(prop) & CheckFlags.Synthetic) { // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.Synthetic for (const t of (prop as TransientSymbol).links.containingType!.types) { const p = getPropertyOfType(t, prop.escapedName); const result = p && forEachProperty(p, callback); if (result) { return result; } } return undefined; } return callback(prop); } // Return the declaring class type of a property or undefined if property not declared in class function getDeclaringClass(prop: Symbol) { return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) as InterfaceType : undefined; } // Return the inherited type of the given property or undefined if property doesn't exist in a base class. function getTypeOfPropertyInBaseClass(property: Symbol) { const classType = getDeclaringClass(property); const baseClassType = classType && getBaseTypes(classType)[0]; return baseClassType && getTypeOfPropertyOfType(baseClassType, property.escapedName); } // Return true if some underlying source property is declared in a class that derives // from the given base class. function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type | undefined) { return forEachProperty(prop, sp => { const sourceClass = getDeclaringClass(sp); return sourceClass ? hasBaseType(sourceClass, baseClass) : false; }); } // Return true if source property is a valid override of protected parts of target property. function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) { return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ? !isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false); } // Return true if the given class derives from each of the declaring classes of the protected // constituents of the given property. function isClassDerivedFromDeclaringClasses(checkClass: T, prop: Symbol, writing: boolean) { return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p, writing) & ModifierFlags.Protected ? !hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass; } // Return true if the given type is deeply nested. We consider this to be the case when the given stack contains // maxDepth or more occurrences of types with the same recursion identity as the given type. The recursion identity // provides a shared identity for type instantiations that repeat in some (possibly infinite) pattern. For example, // in `type Deep = { next: Deep> }`, repeatedly referencing the `next` property leads to an infinite // sequence of ever deeper instantiations with the same recursion identity (in this case the symbol associated with // the object type literal). // A homomorphic mapped type is considered deeply nested if its target type is deeply nested, and an intersection is // considered deeply nested if any constituent of the intersection is deeply nested. // It is possible, though highly unlikely, for the deeply nested check to be true in a situation where a chain of // instantiations is not infinitely expanding. Effectively, we will generate a false positive when two types are // structurally equal to at least maxDepth levels, but unequal at some level beyond that. function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { if (depth >= maxDepth) { if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) { type = getMappedTargetWithSymbol(type); } if (type.flags & TypeFlags.Intersection) { return some((type as IntersectionType).types, t => isDeeplyNestedType(t, stack, depth, maxDepth)); } const identity = getRecursionIdentity(type); let count = 0; let lastTypeId = 0; for (let i = 0; i = lastTypeId) { count++; if (count >= maxDepth) { return true; } } lastTypeId = t.id; } } } return false; } // Unwrap nested homomorphic mapped types and return the deepest target type that has a symbol. This better // preserves unique type identities for mapped types applied to explicitly written object literals. For example // in `Mapped}>}>`, each of the mapped type applications will have a // unique recursion identity (that of their target object type literal) and thus avoid appearing deeply nested. function getMappedTargetWithSymbol(type: Type) { let target; while ( (getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped && (target = getModifiersTypeFromMappedType(type as MappedType)) && (target.symbol || target.flags & TypeFlags.Intersection && some((target as IntersectionType).types, t => !!t.symbol)) ) { type = target; } return type; } function hasMatchingRecursionIdentity(type: Type, identity: object): boolean { if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) { type = getMappedTargetWithSymbol(type); } if (type.flags & TypeFlags.Intersection) { return some((type as IntersectionType).types, t => hasMatchingRecursionIdentity(t, identity)); } return getRecursionIdentity(type) === identity; } // The recursion identity of a type is an object identity that is shared among multiple instantiations of the type. // We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with // the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all // instantiations of that type have the same recursion identity. The default recursion identity is the object // identity of the type, meaning that every type is unique. Generally, types with constituents that could circularly // reference the type have a recursion identity that differs from the object identity. function getRecursionIdentity(type: Type): object { // Object and array literals are known not to contain recursive references and don't need a recursion identity. if (type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) { if (getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).node) { // Deferred type references are tracked through their associated AST node. This gives us finer // granularity than using their associated target because each manifest type reference has a // unique AST node. return (type as TypeReference).node!; } if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) { // We track object types that have a symbol by that symbol (representing the origin of the type), but // exclude the static side of a class since it shares its symbol with the instance side. return type.symbol; } if (isTupleType(type)) { return type.target; } } if (type.flags & TypeFlags.TypeParameter) { // We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter // have the same recursion identity. return type.symbol; } if (type.flags & TypeFlags.IndexedAccess) { // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] it is A. do { type = (type as IndexedAccessType).objectType; } while (type.flags & TypeFlags.IndexedAccess); return type; } if (type.flags & TypeFlags.Conditional) { // The root object represents the origin of the conditional type return (type as ConditionalType).root; } return type; } function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False; } function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary { // Two members are considered identical when // - they are public properties with identical names, optionality, and types, // - they are private or protected properties originating in the same declaration and having identical types if (sourceProp === targetProp) { return Ternary.True; } const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier; const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier; if (sourcePropAccessibility !== targetPropAccessibility) { return Ternary.False; } if (sourcePropAccessibility) { if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) { return Ternary.False; } } else { if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { return Ternary.False; } } if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) { return Ternary.False; } return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); } function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) { const sourceParameterCount = getParameterCount(source); const targetParameterCount = getParameterCount(target); const sourceMinArgumentCount = getMinArgumentCount(source); const targetMinArgumentCount = getMinArgumentCount(target); const sourceHasRestParameter = hasEffectiveRestParameter(source); const targetHasRestParameter = hasEffectiveRestParameter(target); // A source signature matches a target signature if the two signatures have the same number of required, // optional, and rest parameters. if ( sourceParameterCount === targetParameterCount && sourceMinArgumentCount === targetMinArgumentCount && sourceHasRestParameter === targetHasRestParameter ) { return true; } // A source signature partially matches a target signature if the target signature has no fewer required // parameters if (partialMatch && sourceMinArgumentCount 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 Ternary): Ternary { return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False : source.type === target.type ? Ternary.True : source.type && target.type ? compareTypes(source.type, target.type) : Ternary.False; } function literalTypesWithSameBaseType(types: Type[]): boolean { let commonBaseType: Type | undefined; for (const t of types) { if (!(t.flags & TypeFlags.Never)) { const baseType = getBaseTypeOfLiteralType(t); commonBaseType ??= baseType; if (baseType === t || baseType !== commonBaseType) { return false; } } } return true; } function getCombinedTypeFlags(types: Type[]): TypeFlags { return reduceLeft(types, (flags, t) => flags | (t.flags & TypeFlags.Union ? getCombinedTypeFlags((t as UnionType).types) : t.flags), 0 as TypeFlags); } function getCommonSupertype(types: Type[]): Type { if (types.length === 1) { return types[0]; } // Remove nullable types from each of the candidates. const primaryTypes = strictNullChecks ? sameMap(types, t => filterType(t, u => !(u.flags & TypeFlags.Nullable))) : types; // When the candidate types are all literal types with the same base type, return a union // of those literal types. Otherwise, return the leftmost type for which no type to the // right is a supertype. const superTypeOrUnion = literalTypesWithSameBaseType(primaryTypes) ? getUnionType(primaryTypes) : getSingleCommonSupertype(primaryTypes); // Add any nullable types that occurred in the candidates back to the result. return primaryTypes === types ? superTypeOrUnion : getNullableType(superTypeOrUnion, getCombinedTypeFlags(types) & TypeFlags.Nullable); } function getSingleCommonSupertype(types: Type[]) { // First, find the leftmost type for which no type to the right is a strict supertype, and if that // type is a strict supertype of all other candidates, return it. Otherwise, return the leftmost type // for which no type to the right is a (regular) supertype. const candidate = reduceLeft(types, (s, t) => isTypeStrictSubtypeOf(s, t) ? t : s)!; return every(types, t => t === candidate || isTypeStrictSubtypeOf(t, candidate)) ? candidate : reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!; } // Return the leftmost type for which no type to the right is a subtype. function getCommonSubtype(types: Type[]) { return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!; } function isArrayType(type: Type): type is TypeReference { return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type as TypeReference).target === globalArrayType || (type as TypeReference).target === globalReadonlyArrayType); } function isReadonlyArrayType(type: Type): boolean { return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type as TypeReference).target === globalReadonlyArrayType; } function isArrayOrTupleType(type: Type): type is TypeReference { return isArrayType(type) || isTupleType(type); } function isMutableArrayOrTuple(type: Type): boolean { return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly; } function getElementTypeOfArrayType(type: Type): Type | undefined { return isArrayType(type) ? getTypeArguments(type)[0] : undefined; } function isArrayLikeType(type: Type): boolean { // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, // or if it is not the undefined or null type and if it is assignable to ReadonlyArray return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); } function isMutableArrayLikeType(type: Type): boolean { // A type is mutable-array-like if it is a reference to the global Array type, or if it is not the // any, undefined or null type and if it is assignable to Array return isMutableArrayOrTuple(type) || !(type.flags & (TypeFlags.Any | TypeFlags.Nullable)) && isTypeAssignableTo(type, anyArrayType); } function getSingleBaseForNonAugmentingSubtype(type: Type) { if (!(getObjectFlags(type) & ObjectFlags.Reference) || !(getObjectFlags((type as TypeReference).target) & ObjectFlags.ClassOrInterface)) { return undefined; } if (getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeCalculated) { return getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeExists ? (type as TypeReference).cachedEquivalentBaseType : undefined; } (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeCalculated; const target = (type as TypeReference).target as InterfaceType; if (getObjectFlags(target) & ObjectFlags.Class) { const baseTypeNode = getBaseTypeNodeOfClass(target); // A base type expression may circularly reference the class itself (e.g. as an argument to function call), so we only // check for base types specified as simple qualified names. if (baseTypeNode && baseTypeNode.expression.kind !== SyntaxKind.Identifier && baseTypeNode.expression.kind !== SyntaxKind.PropertyAccessExpression) { return undefined; } } const bases = getBaseTypes(target); if (bases.length !== 1) { return undefined; } if (getMembersOfSymbol(type.symbol).size) { return undefined; // If the interface has any members, they may subtype members in the base, so we should do a full structural comparison } let instantiatedBase = !length(target.typeParameters) ? bases[0] : instantiateType(bases[0], createTypeMapper(target.typeParameters!, getTypeArguments(type as TypeReference).slice(0, target.typeParameters!.length))); if (length(getTypeArguments(type as TypeReference)) > length(target.typeParameters)) { instantiatedBase = getTypeWithThisArgument(instantiatedBase, last(getTypeArguments(type as TypeReference))); } (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeExists; return (type as TypeReference).cachedEquivalentBaseType = instantiatedBase; } function isEmptyLiteralType(type: Type): boolean { return strictNullChecks ? type === implicitNeverType : type === undefinedWideningType; } function isEmptyArrayLiteralType(type: Type): boolean { const elementType = getElementTypeOfArrayType(type); return !!elementType && isEmptyLiteralType(elementType); } function isTupleLikeType(type: Type): boolean { let lengthType; return isTupleType(type) || !!getPropertyOfType(type, "0" as __String) || isArrayLikeType(type) && !!(lengthType = getTypeOfPropertyOfType(type, "length" as __String)) && everyType(lengthType, t => !!(t.flags & TypeFlags.NumberLiteral)); } function isArrayOrTupleLikeType(type: Type): boolean { return isArrayLikeType(type) || isTupleLikeType(type); } function getTupleElementType(type: Type, index: number) { const propType = getTypeOfPropertyOfType(type, "" + index as __String); if (propType) { return propType; } if (everyType(type, isTupleType)) { return getTupleElementTypeOutOfStartCount(type, index, compilerOptions.noUncheckedIndexedAccess ? undefinedType : undefined); } return undefined; } function isNeitherUnitTypeNorNever(type: Type): boolean { return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); } function isUnitType(type: Type): boolean { return !!(type.flags & TypeFlags.Unit); } function isUnitLikeType(type: Type): boolean { // Intersections that reduce to 'never' (e.g. 'T & null' where 'T extends {}') are not unit types. const t = getBaseConstraintOrType(type); // Scan intersections such that tagged literal types are considered unit types. return t.flags & TypeFlags.Intersection ? some((t as IntersectionType).types, isUnitType) : isUnitType(t); } function extractUnitType(type: Type) { return type.flags & TypeFlags.Intersection ? find((type as IntersectionType).types, isUnitType) || type : type; } function isLiteralType(type: Type): boolean { return type.flags & TypeFlags.Boolean ? true : type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type as UnionType).types, isUnitType) : isUnitType(type); } function getBaseTypeOfLiteralType(type: Type): Type { return type.flags & TypeFlags.EnumLike ? getBaseTypeOfEnumLikeType(type as LiteralType) : type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType : type.flags & TypeFlags.NumberLiteral ? numberType : type.flags & TypeFlags.BigIntLiteral ? bigintType : type.flags & TypeFlags.BooleanLiteral ? booleanType : type.flags & TypeFlags.Union ? getBaseTypeOfLiteralTypeUnion(type as UnionType) : type; } function getBaseTypeOfLiteralTypeUnion(type: UnionType) { const key = `B${getTypeId(type)}`; return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType)); } // This like getBaseTypeOfLiteralType, but instead treats enum literals as strings/numbers instead // of returning their enum base type (which depends on the types of other literals in the enum). function getBaseTypeOfLiteralTypeForComparison(type: Type): Type { return type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType : type.flags & (TypeFlags.NumberLiteral | TypeFlags.Enum) ? numberType : type.flags & TypeFlags.BigIntLiteral ? bigintType : type.flags & TypeFlags.BooleanLiteral ? booleanType : type.flags & TypeFlags.Union ? mapType(type, getBaseTypeOfLiteralTypeForComparison) : type; } function getWidenedLiteralType(type: Type): Type { return type.flags & TypeFlags.EnumLike && isFreshLiteralType(type) ? getBaseTypeOfEnumLikeType(type as LiteralType) : type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType : type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType : type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType : type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType : type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedLiteralType) : type; } function getWidenedUniqueESSymbolType(type: Type): Type { return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedUniqueESSymbolType) : type; } function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { if (!isLiteralOfContextualType(type, contextualType)) { type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); } return getRegularTypeOfLiteralType(type); } function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) { if (type && isUnitType(type)) { const contextualType = !contextualSignatureReturnType ? undefined : isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) : contextualSignatureReturnType; type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } return type; } function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) { if (type && isUnitType(type)) { const contextualType = !contextualSignatureReturnType ? undefined : getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator); type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } return type; } /** * Check if a Type was written as a tuple type literal. * Prefer using isTupleLikeType() unless the use of `elementTypes`/`getTypeArguments` is required. */ function isTupleType(type: Type): type is TupleTypeReference { return !!(getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target.objectFlags & ObjectFlags.Tuple); } function isGenericTupleType(type: Type): type is TupleTypeReference { return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic); } function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference { return isGenericTupleType(type) && type.target.elementFlags.length === 1; } function getRestTypeOfTupleType(type: TupleTypeReference) { return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); } function getTupleElementTypeOutOfStartCount(type: Type, index: number, undefinedOrMissingType: Type | undefined) { return mapType(type, t => { const tupleType = t as TupleTypeReference; const restType = getRestTypeOfTupleType(tupleType); if (!restType) { return undefinedType; } if (undefinedOrMissingType && index >= getTotalFixedElementCount(tupleType.target)) { return getUnionType([restType, undefinedOrMissingType]); } return restType; }); } function getRestArrayTypeOfTupleType(type: TupleTypeReference) { const restType = getRestTypeOfTupleType(type); return restType && createArrayType(restType); } function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false, noReductions = false) { const length = getTypeReferenceArity(type) - endSkipCount; if (index (f & ElementFlags.Variable) === (t2.target.elementFlags[i] & ElementFlags.Variable)); } function isZeroBigInt({ value }: BigIntLiteralType) { return value.base10Value === "0"; } function removeDefinitelyFalsyTypes(type: Type): Type { return filterType(type, t => hasTypeFacts(t, TypeFacts.Truthy)); } function extractDefinitelyFalsyTypes(type: Type): Type { return mapType(type, getDefinitelyFalsyPartOfType); } function getDefinitelyFalsyPartOfType(type: Type): Type { return type.flags & TypeFlags.String ? emptyStringType : type.flags & TypeFlags.Number ? zeroType : type.flags & TypeFlags.BigInt ? zeroBigIntType : type === regularFalseType || type === falseType || type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null | TypeFlags.AnyOrUnknown) || type.flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === "" || type.flags & TypeFlags.NumberLiteral && (type as NumberLiteralType).value === 0 || type.flags & TypeFlags.BigIntLiteral && isZeroBigInt(type as BigIntLiteralType) ? type : neverType; } /** * Add undefined or null or both to a type if they are missing. * @param type - type to add undefined and/or null to if not present * @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both */ function getNullableType(type: Type, flags: TypeFlags): Type { const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null); return missing === 0 ? type : missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) : missing === TypeFlags.Null ? getUnionType([type, nullType]) : getUnionType([type, undefinedType, nullType]); } function getOptionalType(type: Type, isProperty = false): Type { Debug.assert(strictNullChecks); const missingOrUndefined = isProperty ? undefinedOrMissingType : undefinedType; return type === missingOrUndefined || type.flags & TypeFlags.Union && (type as UnionType).types[0] === missingOrUndefined ? type : getUnionType([type, missingOrUndefined]); } function getGlobalNonNullableTypeInstantiation(type: Type) { if (!deferredGlobalNonNullableTypeAlias) { deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol; } return deferredGlobalNonNullableTypeAlias !== unknownSymbol ? getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]) : getIntersectionType([type, emptyObjectType]); } function getNonNullableType(type: Type): Type { return strictNullChecks ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } function addOptionalTypeMarker(type: Type) { return strictNullChecks ? getUnionType([type, optionalType]) : type; } function removeOptionalTypeMarker(type: Type): Type { return strictNullChecks ? removeType(type, optionalType) : type; } function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; } function getOptionalExpressionType(exprType: Type, expression: Expression) { return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : exprType; } function removeMissingType(type: Type, isOptional: boolean) { return exactOptionalPropertyTypes && isOptional ? removeType(type, missingType) : type; } function containsMissingType(type: Type) { return type === missingType || !!(type.flags & TypeFlags.Union) && (type as UnionType).types[0] === missingType; } function removeMissingOrUndefinedType(type: Type): Type { return exactOptionalPropertyTypes ? removeType(type, missingType) : getTypeWithFacts(type, TypeFacts.NEUndefined); } /** * Is source potentially coercible to target type under `==`. * Assumes that `source` is a constituent of a union, hence * the boolean literal flag on the LHS, but not on the RHS. * * This does not fully replicate the semantics of `==`. The * intention is to catch cases that are clearly not right. * * Comparing (string | number) to number should not remove the * string element. * * Comparing (string | number) to 1 will remove the string * element, though this is not sound. This is a pragmatic * choice. * * @see narrowTypeByEquality * * @param source * @param target */ function isCoercibleUnderDoubleEquals(source: Type, target: Type): boolean { return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0) && ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0); } /** * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module * with no call or construct signatures. */ function isObjectTypeWithInferableIndex(type: Type): boolean { const objectFlags = getObjectFlags(type); return type.flags & TypeFlags.Intersection ? every((type as IntersectionType).types, isObjectTypeWithInferableIndex) : !!( type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && !(type.symbol.flags & SymbolFlags.Class) && !typeHasCallOrConstructSignatures(type) ) || !!( objectFlags & ObjectFlags.ObjectRestType ) || !!(objectFlags & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); } function createSymbolWithType(source: Symbol, type: Type | undefined) { const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly); symbol.declarations = source.declarations; symbol.parent = source.parent; symbol.links.type = type; symbol.links.target = source; if (source.valueDeclaration) { symbol.valueDeclaration = source.valueDeclaration; } const nameType = getSymbolLinks(source).nameType; if (nameType) { symbol.links.nameType = nameType; } return symbol; } function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { const members = createSymbolTable(); for (const property of getPropertiesOfObjectType(type)) { const original = getTypeOfSymbol(property); const updated = f(original); members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated)); } return members; } /** * If the the provided object literal is subject to the excess properties check, * create a new that is exempt. Recursively mark object literal members as exempt. * Leave signatures alone since they are not subject to the check. */ function getRegularTypeOfObjectLiteral(type: Type): Type { if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) { return type; } const regularType = (type as FreshObjectLiteralType).regularType; if (regularType) { return regularType; } const resolved = type as ResolvedType; const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral); const regularNew = createAnonymousType(resolved.symbol, members, resolved.callSignatures, resolved.constructSignatures, resolved.indexInfos); regularNew.flags = resolved.flags; regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral; (type as FreshObjectLiteralType).regularType = regularNew; return regularNew; } function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext { return { parent, propertyName, siblings, resolvedProperties: undefined }; } function getSiblingsOfContext(context: WideningContext): Type[] { if (!context.siblings) { const siblings: Type[] = []; for (const type of getSiblingsOfContext(context.parent!)) { if (isObjectLiteralType(type)) { const prop = getPropertyOfObjectType(type, context.propertyName!); if (prop) { forEachType(getTypeOfSymbol(prop), t => { siblings.push(t); }); } } } context.siblings = siblings; } return context.siblings; } function getPropertiesOfContext(context: WideningContext): Symbol[] { if (!context.resolvedProperties) { const names = new Map<__string symbol>(); for (const t of getSiblingsOfContext(context)) { if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) { for (const prop of getPropertiesOfType(t)) { names.set(prop.escapedName, prop); } } } context.resolvedProperties = arrayFrom(names.values()); } return context.resolvedProperties; } function getWidenedProperty(prop: Symbol, context: WideningContext | undefined): Symbol { if (!(prop.flags & SymbolFlags.Property)) { // Since get accessors already widen their return value there is no need to // widen accessor based properties here. return prop; } const original = getTypeOfSymbol(prop); const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined); const widened = getWidenedTypeWithContext(original, propContext); return widened === original ? prop : createSymbolWithType(prop, widened); } function getUndefinedProperty(prop: Symbol) { const cached = undefinedProperties.get(prop.escapedName); if (cached) { return cached; } const result = createSymbolWithType(prop, undefinedOrMissingType); result.flags |= SymbolFlags.Optional; undefinedProperties.set(prop.escapedName, result); return result; } function getWidenedTypeOfObjectLiteral(type: Type, context: WideningContext | undefined): Type { const members = createSymbolTable(); for (const prop of getPropertiesOfObjectType(type)) { members.set(prop.escapedName, getWidenedProperty(prop, context)); } if (context) { for (const prop of getPropertiesOfContext(context)) { if (!members.has(prop.escapedName)) { members.set(prop.escapedName, getUndefinedProperty(prop)); } } } const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly, info.declaration, info.components))); result.objectFlags |= getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType); // Retain js literal flag through widening return result; } function getWidenedType(type: Type) { return getWidenedTypeWithContext(type, /*context*/ undefined); } function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type { if (getObjectFlags(type) & ObjectFlags.RequiresWidening) { if (context === undefined && type.widened) { return type.widened; } let result: Type | undefined; if (type.flags & (TypeFlags.Any | TypeFlags.Nullable)) { result = anyType; } else if (isObjectLiteralType(type)) { result = getWidenedTypeOfObjectLiteral(type, context); } else if (type.flags & TypeFlags.Union) { const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type as UnionType).types); const widenedTypes = sameMap((type as UnionType).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext)); // Widening an empty object literal transitions from a highly restrictive type to // a highly inclusive one. For that reason we perform subtype reduction here if the // union includes empty object types (e.g. reducing {} | string to just {}). result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); } else if (type.flags & TypeFlags.Intersection) { result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType)); } else if (isArrayOrTupleType(type)) { result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType)); } if (result && context === undefined) { type.widened = result; } return result || type; } return type; } /** * Reports implicit any errors that occur as a result of widening 'null' and 'undefined' * to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to * getWidenedType. But in some cases getWidenedType is called without reporting errors * (type argument inference is an example). * * The return value indicates whether an error was in fact reported. The particular circumstances * are on a best effort basis. Currently, if the null or undefined that causes widening is inside * an object literal property (arbitrarily deeply), this function reports an error. If no error is * reported, reportImplicitAnyError is a suitable fallback to report a general error. */ function reportWideningErrorsInType(type: Type): boolean { let errorReported = false; if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) { if (type.flags & TypeFlags.Union) { if (some((type as UnionType).types, isEmptyObjectType)) { errorReported = true; } else { for (const t of (type as UnionType).types) { errorReported ||= reportWideningErrorsInType(t); } } } else if (isArrayOrTupleType(type)) { for (const t of getTypeArguments(type)) { errorReported ||= reportWideningErrorsInType(t); } } else if (isObjectLiteralType(type)) { for (const p of getPropertiesOfObjectType(type)) { const t = getTypeOfSymbol(p); if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { errorReported = reportWideningErrorsInType(t); if (!errorReported) { // we need to account for property types coming from object literal type normalization in unions const valueDeclaration = p.declarations?.find(d => d.symbol.valueDeclaration?.parent === type.symbol.valueDeclaration); if (valueDeclaration) { error(valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); errorReported = true; } } } } } } return errorReported; } function reportImplicitAny(declaration: Declaration, type: Type, wideningKind?: WideningKind) { const typeAsString = typeToString(getWidenedType(type)); if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) { // Only report implicit any errors/suggestions in TS and ts-check JS files return; } let diagnostic: DiagnosticMessage; switch (declaration.kind) { case SyntaxKind.BinaryExpression: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; break; case SyntaxKind.Parameter: const param = declaration as ParameterDeclaration; if (isIdentifier(param.name)) { const originalKeywordKind = identifierToKeywordKind(param.name); if ( (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && param.parent.parameters.includes(param) && (resolveName(param, param.name.escapedText, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*isUse*/ true) || originalKeywordKind && isTypeNodeKind(originalKeywordKind)) ) { const newName = "arg" + param.parent.parameters.indexOf(param); const typeName = declarationNameToString(param.name) + (param.dotDotDotToken ? "[]" : ""); errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, typeName); return; } } diagnostic = (declaration as ParameterDeclaration).dotDotDotToken ? noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage : noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; break; case SyntaxKind.BindingElement: diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type; if (!noImplicitAny) { // Don't issue a suggestion for binding elements since the codefix doesn't yet support them. return; } break; case SyntaxKind.JSDocFunctionType: error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); return; case SyntaxKind.JSDocSignature: if (noImplicitAny && isJSDocOverloadTag(declaration.parent)) { error(declaration.parent.tagName, Diagnostics.This_overload_implicitly_returns_the_type_0_because_it_lacks_a_return_type_annotation, typeAsString); } return; case SyntaxKind.FunctionDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: if (noImplicitAny && !(declaration as NamedDeclaration).name) { if (wideningKind === WideningKind.GeneratorYield) { error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_Consider_supplying_a_return_type_annotation, typeAsString); } else { error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); } return; } diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage : wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type : Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type; break; case SyntaxKind.MappedType: if (noImplicitAny) { error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type); } return; default: diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; } errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString); } function shouldReportErrorsFromWideningWithContextualSignature(declaration: FunctionLikeDeclaration, wideningKind: WideningKind) { const signature = getContextualSignatureForFunctionLikeDeclaration(declaration); if (!signature) { return true; } let returnType = getReturnTypeOfSignature(signature); const flags = getFunctionFlags(declaration); switch (wideningKind) { case WideningKind.FunctionReturn: if (flags & FunctionFlags.Generator) { returnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, !!(flags & FunctionFlags.Async)) ?? returnType; } else if (flags & FunctionFlags.Async) { returnType = getAwaitedTypeNoAlias(returnType) ?? returnType; } return isGenericType(returnType); case WideningKind.GeneratorYield: const yieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, !!(flags & FunctionFlags.Async)); return !!yieldType && isGenericType(yieldType); case WideningKind.GeneratorNext: const nextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, !!(flags & FunctionFlags.Async)); return !!nextType && isGenericType(nextType); } return false; } function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) { addLazyDiagnostic(() => { if (noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType) { if (!wideningKind || isFunctionLikeDeclaration(declaration) && shouldReportErrorsFromWideningWithContextualSignature(declaration, wideningKind)) { // Report implicit any error within type if possible, otherwise report error on declaration if (!reportWideningErrorsInType(type)) { reportImplicitAny(declaration, type, wideningKind); } } } }); } function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { const sourceCount = getParameterCount(source); const targetCount = getParameterCount(target); const sourceRestType = getEffectiveRestType(source); const targetRestType = getEffectiveRestType(target); const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount; const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount); const sourceThisType = getThisTypeOfSignature(source); if (sourceThisType) { const targetThisType = getThisTypeOfSignature(target); if (targetThisType) { callback(sourceThisType, targetThisType); } } for (let i = 0; i void) { const targetTypePredicate = getTypePredicateOfSignature(target); if (targetTypePredicate) { const sourceTypePredicate = getTypePredicateOfSignature(source); if (sourceTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { callback(sourceTypePredicate.type, targetTypePredicate.type); return; } } const targetReturnType = getReturnTypeOfSignature(target); if (couldContainTypeVariables(targetReturnType)) { callback(getReturnTypeOfSignature(source), targetReturnType); } } function createInferenceContext(typeParameters: readonly TypeParameter[], signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext { return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable); } function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); } function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { const context: InferenceContext = { inferences, signature, flags, compareTypes, mapper: reportUnmeasurableMapper, // initialize to a noop mapper so the context object is available, but the underlying object shape is right upon construction nonFixingMapper: reportUnmeasurableMapper, }; context.mapper = makeFixingMapperForContext(context); context.nonFixingMapper = makeNonFixingMapperForContext(context); return context; } function makeFixingMapperForContext(context: InferenceContext) { return makeDeferredTypeMapper( map(context.inferences, i => i.typeParameter), map(context.inferences, (inference, i) => () => { if (!inference.isFixed) { // Before we commit to a particular inference (and thus lock out any further inferences), // we infer from any intra-expression inference sites we have collected. inferFromIntraExpressionSites(context); clearCachedInferences(context.inferences); inference.isFixed = true; } return getInferredType(context, i); }), ); } function makeNonFixingMapperForContext(context: InferenceContext) { return makeDeferredTypeMapper( map(context.inferences, i => i.typeParameter), map(context.inferences, (_, i) => () => { return getInferredType(context, i); }), ); } function clearCachedInferences(inferences: InferenceInfo[]) { for (const inference of inferences) { if (!inference.isFixed) { inference.inferredType = undefined; } } } function addIntraExpressionInferenceSite(context: InferenceContext, node: Expression | MethodDeclaration, type: Type) { (context.intraExpressionInferenceSites ??= []).push({ node, type }); } // We collect intra-expression inference sites within object and array literals to handle cases where // inferred types flow between context sensitive element expressions. For example: // // declare function foo(arg: [(n: number) => T, (x: T) => void]): void; // foo([_a => 0, n => n.toFixed()]); // // Above, both arrow functions in the tuple argument are context sensitive, thus both are omitted from the // pass that collects inferences from the non-context sensitive parts of the arguments. In the subsequent // pass where nothing is omitted, we need to commit to an inference for T in order to contextually type the // parameter in the second arrow function, but we want to first infer from the return type of the first // arrow function. This happens automatically when the arrow functions are discrete arguments (because we // infer from each argument before processing the next), but when the arrow functions are elements of an // object or array literal, we need to perform intra-expression inferences early. function inferFromIntraExpressionSites(context: InferenceContext) { if (context.intraExpressionInferenceSites) { for (const { node, type } of context.intraExpressionInferenceSites) { const contextualType = node.kind === SyntaxKind.MethodDeclaration ? getContextualTypeForObjectLiteralMethod(node as MethodDeclaration, ContextFlags.NoConstraints) : getContextualType(node, ContextFlags.NoConstraints); if (contextualType) { inferTypes(context.inferences, type, contextualType); } } context.intraExpressionInferenceSites = undefined; } } function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo { return { typeParameter, candidates: undefined, contraCandidates: undefined, inferredType: undefined, priority: undefined, topLevel: true, isFixed: false, impliedArity: undefined, }; } function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo { return { typeParameter: inference.typeParameter, candidates: inference.candidates && inference.candidates.slice(), contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(), inferredType: inference.inferredType, priority: inference.priority, topLevel: inference.topLevel, isFixed: inference.isFixed, impliedArity: inference.impliedArity, }; } function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { const inferences = filter(context.inferences, hasInferenceCandidates); return inferences.length ? createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) : undefined; } function getMapperFromContext(context: T): TypeMapper | T & undefined { return context && context.mapper; } // Return true if the given type could possibly reference a type parameter for which // we perform type inference (i.e. a type parameter of a generic function). We cache // results for union and intersection types for performance reasons. function couldContainTypeVariables(type: Type): boolean { const objectFlags = getObjectFlags(type); if (objectFlags & ObjectFlags.CouldContainTypeVariablesComputed) { return !!(objectFlags & ObjectFlags.CouldContainTypeVariables); } const result = !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && ( objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || some(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations || objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType) ) || type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables)); if (type.flags & TypeFlags.ObjectFlagsType) { (type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0); } return result; } function isNonGenericTopLevelType(type: Type) { if (type.aliasSymbol && !type.aliasTypeArguments) { const declaration = getDeclarationOfKind(type.aliasSymbol, SyntaxKind.TypeAliasDeclaration); return !!(declaration && findAncestor(declaration.parent, n => n.kind === SyntaxKind.SourceFile ? true : n.kind === SyntaxKind.ModuleDeclaration ? false : "quit")); } return false; } function isTypeParameterAtTopLevel(type: Type, tp: TypeParameter, depth = 0): boolean { return !!(type === tp || type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, tp, depth)) || depth { if (!(t.flags & TypeFlags.StringLiteral)) { return; } const name = escapeLeadingUnderscores((t as StringLiteralType).value); const literalProp = createSymbol(SymbolFlags.Property, name); literalProp.links.type = anyType; if (t.symbol) { literalProp.declarations = t.symbol.declarations; literalProp.valueDeclaration = t.symbol.valueDeclaration; } members.set(name, literalProp); }); const indexInfos = type.flags & TypeFlags.String ? [createIndexInfo(stringType, emptyObjectType, /*isReadonly*/ false)] : emptyArray; return createAnonymousType(/*symbol*/ undefined, members, emptyArray, emptyArray, indexInfos); } /** * Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct * an object type with the same set of properties as the source type, where the type of each * property is computed by inferring from the source property type to X for the type * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). */ function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { const cacheKey = source.id + "," + target.id + "," + constraint.id; if (reverseHomomorphicMappedCache.has(cacheKey)) { return reverseHomomorphicMappedCache.get(cacheKey); } const type = createReverseMappedType(source, target, constraint); reverseHomomorphicMappedCache.set(cacheKey, type); return type; } // We consider a type to be partially inferable if it isn't marked non-inferable or if it is // an object literal type with at least one property of an inferable type. For example, an object // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive // arrow function, but is considered partially inferable because property 'a' has an inferable type. function isPartiallyInferableType(type: Type): boolean { return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))) || isTupleType(type) && some(getElementTypes(type), isPartiallyInferableType); } function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { // We consider a source type reverse mappable if it has a string index signature or if // it has one or more properties and is of a partially inferable type. if (!(getIndexInfoOfType(source, stringType) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { return undefined; } // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been // applied to the element type(s). if (isArrayType(source)) { const elementType = inferReverseMappedType(getTypeArguments(source)[0], target, constraint); if (!elementType) { return undefined; } return createArrayType(elementType, isReadonlyArrayType(source)); } if (isTupleType(source)) { const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint)); if (!every(elementTypes, (t): t is Type => !!t)) { return undefined; } const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ? sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : source.target.elementFlags; return createTupleType(elementTypes, elementFlags, source.target.readonly, source.target.labeledElementDeclarations); } // For all other object types we infer a new object type where the reverse mapping has been // applied to the type of each property. const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType; reversed.source = source; reversed.mappedType = target; reversed.constraintType = constraint; return reversed; } function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol): Type { const links = getSymbolLinks(symbol); if (!links.type) { links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType) || unknownType; } return links.type; } function inferReverseMappedTypeWorker(sourceType: Type, target: MappedType, constraint: IndexType): Type { const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)) as TypeParameter; const templateType = getTemplateTypeFromMappedType(target); const inference = createInferenceInfo(typeParameter); inferTypes([inference], sourceType, templateType); return getTypeFromInference(inference) || unknownType; } function inferReverseMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { const cacheKey = source.id + "," + target.id + "," + constraint.id; if (reverseMappedCache.has(cacheKey)) { return reverseMappedCache.get(cacheKey) || unknownType; } reverseMappedSourceStack.push(source); reverseMappedTargetStack.push(target); const saveExpandingFlags = reverseExpandingFlags; if (isDeeplyNestedType(source, reverseMappedSourceStack, reverseMappedSourceStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Source; if (isDeeplyNestedType(target, reverseMappedTargetStack, reverseMappedTargetStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Target; let type; if (reverseExpandingFlags !== ExpandingFlags.Both) { type = inferReverseMappedTypeWorker(source, target, constraint); } reverseMappedSourceStack.pop(); reverseMappedTargetStack.pop(); reverseExpandingFlags = saveExpandingFlags; reverseMappedCache.set(cacheKey, type); return type; } function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator { const properties = getPropertiesOfType(target); for (const targetProp of properties) { // TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass if (isStaticPrivateIdentifierProperty(targetProp)) { continue; } if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) { const sourceProp = getPropertyOfType(source, targetProp.escapedName); if (!sourceProp) { yield targetProp; } else if (matchDiscriminantProperties) { const targetType = getTypeOfSymbol(targetProp); if (targetType.flags & TypeFlags.Unit) { const sourceType = getTypeOfSymbol(sourceProp); if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) { yield targetProp; } } } } } } function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { return firstOrUndefinedIterator(getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties)); } function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength || !(target.target.combinedFlags & ElementFlags.Variable) && (!!(source.target.combinedFlags & ElementFlags.Variable) || target.target.fixedLength getStringMappingType(value, memo), source); return mappedSource === source && isMemberOfStringMapping(source, target); } return false; } function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean { if (target.flags & TypeFlags.Intersection) { return every((target as IntersectionType).types, t => t === emptyTypeLiteralType || isValidTypeForTemplateLiteralPlaceholder(source, t)); } if (target.flags & TypeFlags.String || isTypeAssignableTo(source, target)) { return true; } if (source.flags & TypeFlags.StringLiteral) { const value = (source as StringLiteralType).value; return !!(target.flags & TypeFlags.Number && isValidNumberString(value, /*roundTripOnly*/ false) || target.flags & TypeFlags.BigInt && isValidBigIntString(value, /*roundTripOnly*/ false) || target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName || target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(source, target) || target.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)); } if (source.flags & TypeFlags.TemplateLiteral) { const texts = (source as TemplateLiteralType).texts; return texts.length === 2 && texts[0] === "" && texts[1] === "" && isTypeAssignableTo((source as TemplateLiteralType).types[0], target); } return false; } function inferTypesFromTemplateLiteralType(source: Type, target: TemplateLiteralType): Type[] | undefined { return source.flags & TypeFlags.StringLiteral ? inferFromLiteralPartsToTemplateLiteral([(source as StringLiteralType).value], emptyArray, target) : source.flags & TypeFlags.TemplateLiteral ? arrayIsEqualTo((source as TemplateLiteralType).texts, target.texts) ? map((source as TemplateLiteralType).types, (s, i) => { return isTypeAssignableTo(getBaseConstraintOrType(s), getBaseConstraintOrType(target.types[i])) ? s : getStringLikeTypeForType(s); }) : inferFromLiteralPartsToTemplateLiteral((source as TemplateLiteralType).texts, (source as TemplateLiteralType).types, target) : undefined; } function isTypeMatchedByTemplateLiteralType(source: Type, target: TemplateLiteralType): boolean { const inferences = inferTypesFromTemplateLiteralType(source, target); return !!inferences && every(inferences, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, target.types[i])); } function getStringLikeTypeForType(type: Type) { return type.flags & (TypeFlags.Any | TypeFlags.StringLike) ? type : getTemplateLiteralType(["", ""], [type]); } // This function infers from the text parts and type parts of a source literal to a target template literal. The number // of text parts is always one more than the number of type parts, and a source string literal is treated as a source // with one text part and zero type parts. The function returns an array of inferred string or template literal types // corresponding to the placeholders in the target template literal, or undefined if the source doesn't match the target. // // We first check that the starting source text part matches the starting target text part, and that the ending source // text part ends matches the ending target text part. We then iterate through the remaining target text parts, finding // a match for each in the source and inferring string or template literal types created from the segments of the source // that occur between the matches. During this iteration, seg holds the index of the current text part in the sourceTexts // array and pos holds the current character position in the current text part. // // Consider inference from type `.>` to type ``, i.e. // sourceTexts = ['.>'] // sourceTypes = [string, number, number] // target.texts = [''] // We first match '' 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 ``. The remainder of the source makes up the second // inference, the template literal type ``. 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 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 ; let sourceStack: Type[]; let targetStack: Type[]; let expandingFlags = ExpandingFlags.None; inferFromTypes(originalSource, originalTarget); function inferFromTypes(source: Type, target: Type): void { if (!couldContainTypeVariables(target) || isNoInferType(target)) { return; } if (source === wildcardType || source === blockedStringType) { // We are inferring from an 'any' type. We want to infer this type for every type parameter // referenced in the target type, so we record it as the propagation type and infer from the // target to itself. Then, as we find candidates we substitute the propagation type. const savePropagationType = propagationType; propagationType = source; inferFromTypes(target, target); propagationType = savePropagationType; return; } if (source.aliasSymbol && source.aliasSymbol === target.aliasSymbol) { if (source.aliasTypeArguments) { // Source and target are types originating in the same generic type alias declaration. // Simply infer from source type arguments to target type arguments, with defaults applied. const params = getSymbolLinks(source.aliasSymbol).typeParameters!; const minParams = getMinTypeArgumentCount(params); const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); inferFromTypeArguments(sourceTypes, targetTypes!, getAliasVariances(source.aliasSymbol)); } // And if there weren't any type arguments, there's no reason to run inference as the types must be the same. return; } if (source === target && source.flags & TypeFlags.UnionOrIntersection) { // When source and target are the same union or intersection type, just relate each constituent // type to itself. for (const t of (source as UnionOrIntersectionType).types) { inferFromTypes(t, t); } return; } if (target.flags & TypeFlags.Union) { // First, infer between identically matching source and target constituents and remove the // matching types. const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (source as UnionType).types : [source], (target as UnionType).types, isTypeOrBaseIdenticalTo); // Next, infer between closely matching source and target constituents and remove // the matching types. Types closely match when they are instantiations of the same // object type or instantiations of the same type alias. const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); if (targets.length === 0) { return; } target = getUnionType(targets); if (sources.length === 0) { // All source constituents have been matched and there is nothing further to infer from. // However, simply making no inferences is undesirable because it could ultimately mean // inferring a type parameter constraint. Instead, make a lower priority inference from // the full source to whatever remains in the target. For example, when inferring from // string to 'string | T', make a lower priority inference of string for T. inferWithPriority(source, target, InferencePriority.NakedTypeVariable); return; } source = getUnionType(sources); } else if (target.flags & TypeFlags.Intersection && !every((target as IntersectionType).types, isNonGenericObjectType)) { // We reduce intersection types unless they're simple combinations of object types. For example, // when inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the // string[] on the source side and infer string for T. if (!(source.flags & TypeFlags.Union)) { // Infer between identically matching source and target constituents and remove the matching types. const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types : [source], (target as IntersectionType).types, isTypeIdenticalTo); if (sources.length === 0 || targets.length === 0) { return; } source = getIntersectionType(sources); target = getIntersectionType(targets); } } if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { if (isNoInferType(target)) { return; } target = getActualTypeVariable(target); } if (target.flags & TypeFlags.TypeVariable) { // Skip inference if the source is "blocked", which is used by the language service to // prevent inference on nodes currently being edited. if (isFromInferenceBlockedSource(source)) { return; } const inference = getInferenceInfoForType(target); if (inference) { // If target is a type parameter, make an inference, unless the source type contains // a "non-inferrable" type. Types with this flag set are markers used to prevent inference. // // For example: // - anyFunctionType is a wildcard type that's used to avoid contextually typing functions; // it's internal, so should not be exposed to the user by adding it as a candidate. // - autoType (and autoArrayType) is a special "any" used in control flow; like anyFunctionType, // it's internal and should not be observable. // - silentNeverType is returned by getInferredType when instantiating a generic function for // inference (and a type variable has no mapping). // // This flag is infectious; if we produce Box (where never is silentNeverType), Box is // also non-inferrable. // // As a special case, also ignore nonInferrableAnyType, which is a special form of the any type // used as a stand-in for binding elements when they are being inferred. if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType) { return; } if (!inference.isFixed) { const candidate = propagationType || source; if (candidate === blockedStringType) { return; } if (inference.priority === undefined || priority = { next: Deep> }` and target type `type Loop = { next: Loop }`. // We would then infer between the types of the `next` property: `Deep>` = `{ next: Deep>> }` and `Loop` = `{ next: Loop }`. // We will then infer again between the types of the `next` property: // `Deep>>` and `Loop`, and so on, such that we would be forever inferring // between instantiations of the same types `Deep` and `Loop`. // In particular, we would be inferring from increasingly deep instantiations of `Deep` to `Loop`, // such that we would go on inferring forever, even though we would never infer // between the same pair of types. function invokeOnce(source: Source, target: Target, action: (source: Source, target: Target) => void) { const key = source.id + "," + target.id; const status = visited && visited.get(key); if (status !== undefined) { inferencePriority = Math.min(inferencePriority, status); return; } (visited || (visited = new Map())).set(key, InferencePriority.Circularity); const saveInferencePriority = inferencePriority; inferencePriority = InferencePriority.MaxValue; // We stop inferring and report a circularity if we encounter duplicate recursion identities on both // the source side and the target side. const saveExpandingFlags = expandingFlags; (sourceStack ??= []).push(source); (targetStack ??= []).push(target); if (isDeeplyNestedType(source, sourceStack, sourceStack.length, 2)) expandingFlags |= ExpandingFlags.Source; if (isDeeplyNestedType(target, targetStack, targetStack.length, 2)) expandingFlags |= ExpandingFlags.Target; if (expandingFlags !== ExpandingFlags.Both) { action(source, target); } else { inferencePriority = InferencePriority.Circularity; } targetStack.pop(); sourceStack.pop(); expandingFlags = saveExpandingFlags; visited.set(key, inferencePriority); inferencePriority = Math.min(inferencePriority, saveInferencePriority); } function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] { let matchedSources: Type[] | undefined; let matchedTargets: Type[] | undefined; for (const t of targets) { for (const s of sources) { if (matches(s, t)) { inferFromTypes(s, t); matchedSources = appendIfUnique(matchedSources, s); matchedTargets = appendIfUnique(matchedTargets, t); } } } return [ matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources, matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets, ]; } function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { const count = sourceTypes.length !!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 matched[i] ? undefined : s); if (unmatched.length) { inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); return; } } } else { // We infer from types that are not naked type variables first so that inferences we // make from nested naked type variables and given slightly higher priority by virtue // of being first in the candidates array. for (const t of targets) { if (getInferenceInfoForType(t)) { typeVariableCount++; } else { inferFromTypes(source, t); } } } // Inferences directly to naked type variables are given lower priority as they are // less specific. For example, when inferring from Promise to T | Promise, // we want to infer string for T, not Promise | string. For intersection types // we only infer to single naked type variables. if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { for (const t of targets) { if (getInferenceInfoForType(t)) { inferWithPriority(source, t, InferencePriority.NakedTypeVariable); } } } } function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { if ((constraintType.flags & TypeFlags.Union) || (constraintType.flags & TypeFlags.Intersection)) { let result = false; for (const type of (constraintType as (UnionType | IntersectionType)).types) { result = inferToMappedType(source, target, type) || result; } return result; } if (constraintType.flags & TypeFlags.Index) { // We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X }, // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source // type and then make a secondary inference from that type to T. We make a secondary inference // such that direct inferences to T get priority over inferences to Partial, for example. const inference = getInferenceInfoForType((constraintType as IndexType).type); if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) { const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType as IndexType); if (inferredType) { // We assign a lower priority to inferences made from types containing non-inferrable // types because we may only have a partial result (i.e. we may have failed to make // reverse inferences for some properties). inferWithPriority( inferredType, inference.typeParameter, getObjectFlags(source) & ObjectFlags.NonInferrableType ? InferencePriority.PartialHomomorphicMappedType : InferencePriority.HomomorphicMappedType, ); } } return true; } if (constraintType.flags & TypeFlags.TypeParameter) { // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type // parameter. First infer from 'keyof S' to K. inferWithPriority(getIndexType(source, /*indexFlags*/ !!source.pattern ? IndexFlags.NoIndexSignatures : IndexFlags.None), constraintType, InferencePriority.MappedTypeConstraint); // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, // where K extends keyof T, we make the same inferences as for a homomorphic mapped type // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a // Pick. const extendedConstraint = getConstraintOfType(constraintType); if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) { return true; } // If no inferences can be made to K's constraint, infer from a union of the property types // in the source to the template type X. const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol); const indexTypes = map(getIndexInfosOfType(source), info => info !== enumNumberIndexInfo ? info.type : neverType); inferFromTypes(getUnionType(concatenate(propTypes, indexTypes)), getTemplateTypeFromMappedType(target)); return true; } return false; } function inferToConditionalType(source: Type, target: ConditionalType) { if (source.flags & TypeFlags.Conditional) { inferFromTypes((source as ConditionalType).checkType, target.checkType); inferFromTypes((source as ConditionalType).extendsType, target.extendsType); inferFromTypes(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target)); inferFromTypes(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target)); } else { const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; inferToMultipleTypesWithPriority(source, targetTypes, target.flags, contravariant ? InferencePriority.ContravariantConditional : 0); } } function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) { const matches = inferTypesFromTemplateLiteralType(source, target); const types = target.types; // When the target template literal contains only placeholders (meaning that inference is intended to extract // single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for // each placeholder such that instantiation with the inferred value(s) produces 'never', a type for which an // assignment check will fail. If we make no inferences, we'll likely end up with the constraint 'string' which, // upon instantiation, would collapse all the placeholders to just 'string', and an assignment check might // succeed. That would be a pointless and confusing outcome. if (matches || every(target.texts, s => s.length === 0)) { for (let i = 0; i flags | t.flags, 0 as TypeFlags); // If the constraint contains `string`, we don't need to look for a more preferred type if (!(allTypeFlags & TypeFlags.String)) { const str = (source as StringLiteralType).value; // If the type contains `number` or a number literal and the string isn't a valid number, exclude numbers if (allTypeFlags & TypeFlags.NumberLike && !isValidNumberString(str, /*roundTripOnly*/ true)) { allTypeFlags &= ~TypeFlags.NumberLike; } // If the type contains `bigint` or a bigint literal and the string isn't a valid bigint, exclude bigints if (allTypeFlags & TypeFlags.BigIntLike && !isValidBigIntString(str, /*roundTripOnly*/ true)) { allTypeFlags &= ~TypeFlags.BigIntLike; } // for each type in the constraint, find the highest priority matching type const matchingType = reduceLeft(constraintTypes, (left, right) => !(right.flags & allTypeFlags) ? left : left.flags & TypeFlags.String ? left : right.flags & TypeFlags.String ? source : left.flags & TypeFlags.TemplateLiteral ? left : right.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, right as TemplateLiteralType) ? source : left.flags & TypeFlags.StringMapping ? left : right.flags & TypeFlags.StringMapping && str === applyStringMapping(right.symbol, str) ? source : left.flags & TypeFlags.StringLiteral ? left : right.flags & TypeFlags.StringLiteral && (right as StringLiteralType).value === str ? right : left.flags & TypeFlags.Number ? left : right.flags & TypeFlags.Number ? getNumberLiteralType(+str) : left.flags & TypeFlags.Enum ? left : right.flags & TypeFlags.Enum ? getNumberLiteralType(+str) : left.flags & TypeFlags.NumberLiteral ? left : right.flags & TypeFlags.NumberLiteral && (right as NumberLiteralType).value === +str ? right : left.flags & TypeFlags.BigInt ? left : right.flags & TypeFlags.BigInt ? parseBigIntLiteralType(str) : left.flags & TypeFlags.BigIntLiteral ? left : right.flags & TypeFlags.BigIntLiteral && pseudoBigIntToString((right as BigIntLiteralType).value) === str ? right : left.flags & TypeFlags.Boolean ? left : right.flags & TypeFlags.Boolean ? str === "true" ? trueType : str === "false" ? falseType : booleanType : left.flags & TypeFlags.BooleanLiteral ? left : right.flags & TypeFlags.BooleanLiteral && (right as IntrinsicType).intrinsicName === str ? right : left.flags & TypeFlags.Undefined ? left : right.flags & TypeFlags.Undefined && (right as IntrinsicType).intrinsicName === str ? right : left.flags & TypeFlags.Null ? left : right.flags & TypeFlags.Null && (right as IntrinsicType).intrinsicName === str ? right : left, neverType as Type); if (!(matchingType.flags & TypeFlags.Never)) { inferFromTypes(matchingType, target); continue; } } } } inferFromTypes(source, target); } } } function inferFromGenericMappedTypes(source: MappedType, target: MappedType) { // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer // from S to T and from X to Y. inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target)); inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target)); const sourceNameType = getNameTypeFromMappedType(source); const targetNameType = getNameTypeFromMappedType(target); if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType); } function inferFromObjectTypes(source: Type, target: Type) { if ( getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target) ) ) { // If source and target are references to the same generic type, infer from type arguments inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target)); return; } if (isGenericMappedType(source) && isGenericMappedType(target)) { inferFromGenericMappedTypes(source, target); } if (getObjectFlags(target) & ObjectFlags.Mapped && !(target as MappedType).declaration.nameType) { const constraintType = getConstraintTypeFromMappedType(target as MappedType); if (inferToMappedType(source, target as MappedType, constraintType)) { return; } } // Infer from the members of source and target only if the two types are possibly related if (!typesDefinitelyUnrelated(source, target)) { if (isArrayOrTupleType(source)) { if (isTupleType(target)) { const sourceArity = getTypeReferenceArity(source); const targetArity = getTypeReferenceArity(target); const elementTypes = getTypeArguments(target); const elementFlags = target.target.elementFlags; // When source and target are tuple types with the same structure (fixed, variadic, and rest are matched // to the same kind in each position), simply infer between the element types. if (isTupleType(source) && isTupleTypeStructureMatching(source, target)) { for (let i = 0; i 0) { // We match source and target signatures from the bottom up, and if the source has fewer signatures // than the target, we infer from the first source signature to the excess target signatures. const targetSignatures = getSignaturesOfType(target, kind); const targetLen = targetSignatures.length; for (let i = 0; i 1) { const objectLiterals = filter(candidates, isObjectOrArrayLiteralType); if (objectLiterals.length) { const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype); return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]); } } return candidates; } function getContravariantInference(inference: InferenceInfo) { return inference.priority! & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!); } function getCovariantInference(inference: InferenceInfo, signature: Signature) { // Extract all object and array literal types and replace them with a single widened and normalized type. const candidates = unionObjectAndArrayLiteralCandidates(inference.candidates!); // We widen inferred literal types if // all inferences were made to top-level occurrences of the type parameter, and // the type parameter has no constraint or its constraint includes no primitive or literal types, and // the type parameter was fixed during inference or does not occur at top-level in the return type. const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter) || isConstTypeVariable(inference.typeParameter); const widenLiteralTypes = !primitiveConstraint && inference.topLevel && (inference.isFixed || !isTypeParameterAtTopLevelInReturnType(signature, inference.typeParameter)); const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) : widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : candidates; // If all inferences were made from a position that implies a combined result, infer a union type. // Otherwise, infer a common supertype. const unwidenedType = inference.priority! & InferencePriority.PriorityImpliesCombination ? getUnionType(baseCandidates, UnionReduction.Subtype) : getCommonSupertype(baseCandidates); return getWidenedType(unwidenedType); } function getInferredType(context: InferenceContext, index: number): Type { const inference = context.inferences[index]; if (!inference.inferredType) { let inferredType: Type | undefined; let fallbackType: Type | undefined; if (context.signature) { const inferredCovariantType = inference.candidates ? getCovariantInference(inference, context.signature) : undefined; const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined; if (inferredCovariantType || inferredContravariantType) { // If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never', // all co-variant inferences are assignable to it (i.e. it isn't one of a conflicting set of candidates), it is // assignable to some contra-variant inference, and no other type parameter is constrained to this type parameter // and has inferences that would conflict. Otherwise, we prefer the contra-variant inference. // Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it // and it would spoil the overall inference. const preferCovariantType = inferredCovariantType && (!inferredContravariantType || !(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any)) && some(inference.contraCandidates, t => isTypeAssignableTo(inferredCovariantType, t)) && every(context.inferences, other => other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter || every(other.candidates, t => isTypeAssignableTo(t, inferredCovariantType)))); inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType; fallbackType = preferCovariantType ? inferredContravariantType : inferredCovariantType; } else if (context.flags & InferenceFlags.NoDefault) { // We use silentNeverType as the wildcard that signals no inferences. inferredType = silentNeverType; } else { // Infer either the default or the empty object type when no inferences were // made. It is important to remember that in this case, inference still // succeeds, meaning there is no error for not having inference candidates. An // inference error only occurs when there are *conflicting* candidates, i.e. // candidates with no common supertype. const defaultType = getDefaultFromTypeParameter(inference.typeParameter); if (defaultType) { // Instantiate the default type. Any forward reference to a type // parameter should be instantiated to the empty object type. inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); } } } else { inferredType = getTypeFromInference(inference); } inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); const constraint = getConstraintOfTypeParameter(inference.typeParameter); if (constraint) { const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { // If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint. inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint; } } clearActiveMapperCaches(); } return inference.inferredType; } function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type { return isInJavaScriptFile ? anyType : unknownType; } function getInferredTypes(context: InferenceContext): Type[] { const result: Type[] = []; for (let i = 0; i isInterfaceDeclaration(n) || isTypeAliasDeclaration(n) || isTypeLiteralNode(n))); } // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers // separated by dots). The key consists of the id of the symbol referenced by the // leftmost identifier followed by zero or more property names separated by dots. // The result is undefined if the reference isn't a dotted name. function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { switch (node.kind) { case SyntaxKind.Identifier: if (!isThisInTypeQuery(node)) { const symbol = getResolvedSymbol(node as Identifier); return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${getSymbolId(symbol)}` : undefined; } // falls through case SyntaxKind.ThisKeyword: return `0|${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}`; case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: return getFlowCacheKey((node as NonNullExpression | ParenthesizedExpression).expression, declaredType, initialType, flowContainer); case SyntaxKind.QualifiedName: const left = getFlowCacheKey((node as QualifiedName).left, declaredType, initialType, flowContainer); return left && `${left}.${(node as QualifiedName).right.escapedText}`; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: const propName = getAccessedPropertyName(node as AccessExpression); if (propName !== undefined) { const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); return key && `${key}.${propName}`; } if (isElementAccessExpression(node) && isIdentifier(node.argumentExpression)) { const symbol = getResolvedSymbol(node.argumentExpression); if (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol)) { const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); return key && `${key}.@${getSymbolId(symbol)}`; } } break; case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: // Handle pseudo-references originating in getNarrowedTypeOfSymbol. return `${getNodeId(node)}#${getTypeId(declaredType)}`; } return undefined; } function isMatchingReference(source: Node, target: Node): boolean { switch (target.kind) { case SyntaxKind.ParenthesizedExpression: case SyntaxKind.NonNullExpression: return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); case SyntaxKind.BinaryExpression: return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) || (isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right)); } switch (source.kind) { case SyntaxKind.MetaProperty: return target.kind === SyntaxKind.MetaProperty && (source as MetaProperty).keywordToken === (target as MetaProperty).keywordToken && (source as MetaProperty).name.escapedText === (target as MetaProperty).name.escapedText; case SyntaxKind.Identifier: case SyntaxKind.PrivateIdentifier: return isThisInTypeQuery(source) ? target.kind === SyntaxKind.ThisKeyword : target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) || (isVariableDeclaration(target) || isBindingElement(target)) && getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfDeclaration(target); case SyntaxKind.ThisKeyword: return target.kind === SyntaxKind.ThisKeyword; case SyntaxKind.SuperKeyword: return target.kind === SyntaxKind.SuperKeyword; case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.SatisfiesExpression: return isMatchingReference((source as NonNullExpression | ParenthesizedExpression | SatisfiesExpression).expression, target); case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: const sourcePropertyName = getAccessedPropertyName(source as AccessExpression); if (sourcePropertyName !== undefined) { const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined; if (targetPropertyName !== undefined) { return targetPropertyName === sourcePropertyName && isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); } } if (isElementAccessExpression(source) && isElementAccessExpression(target) && isIdentifier(source.argumentExpression) && isIdentifier(target.argumentExpression)) { const symbol = getResolvedSymbol(source.argumentExpression); if (symbol === getResolvedSymbol(target.argumentExpression) && (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol))) { return isMatchingReference(source.expression, target.expression); } } break; case SyntaxKind.QualifiedName: return isAccessExpression(target) && (source as QualifiedName).right.escapedText === getAccessedPropertyName(target) && isMatchingReference((source as QualifiedName).left, target.expression); case SyntaxKind.BinaryExpression: return (isBinaryExpression(source) && source.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source.right, target)); } return false; } function getAccessedPropertyName(access: AccessExpression | BindingElement | ParameterDeclaration): __String | undefined { if (isPropertyAccessExpression(access)) { return access.name.escapedText; } if (isElementAccessExpression(access)) { return tryGetElementAccessExpressionName(access); } if (isBindingElement(access)) { const name = getDestructuringPropertyName(access); return name ? escapeLeadingUnderscores(name) : undefined; } if (isParameter(access)) { return ("" + access.parent.parameters.indexOf(access)) as __String; } return undefined; } function tryGetNameFromType(type: Type) { return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName : type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined; } function tryGetElementAccessExpressionName(node: ElementAccessExpression) { return isStringOrNumericLiteralLike(node.argumentExpression) ? escapeLeadingUnderscores(node.argumentExpression.text) : isEntityNameExpression(node.argumentExpression) ? tryGetNameFromEntityNameExpression(node.argumentExpression) : undefined; } function tryGetNameFromEntityNameExpression(node: EntityNameOrEntityNameExpression) { const symbol = resolveEntityName(node, SymbolFlags.Value, /*ignoreErrors*/ true); if (!symbol || !(isConstantVariable(symbol) || (symbol.flags & SymbolFlags.EnumMember))) return undefined; const declaration = symbol.valueDeclaration; if (declaration === undefined) return undefined; const type = tryGetTypeFromEffectiveTypeNode(declaration); if (type) { const name = tryGetNameFromType(type); if (name !== undefined) { return name; } } if (hasOnlyExpressionInitializer(declaration) && isBlockScopedNameDeclaredBeforeUse(declaration, node)) { const initializer = getEffectiveInitializer(declaration); if (initializer) { const initializerType = isBindingPattern(declaration.parent) ? getTypeForBindingElement(declaration as BindingElement) : getTypeOfExpression(initializer); return initializerType && tryGetNameFromType(initializerType); } if (isEnumMember(declaration)) { return getTextOfPropertyName(declaration.name); } } return undefined; } function containsMatchingReference(source: Node, target: Node) { while (isAccessExpression(source)) { source = source.expression; if (isMatchingReference(source, target)) { return true; } } return false; } function optionalChainContainsReference(source: Node, target: Node) { while (isOptionalChain(source)) { source = source.expression; if (isMatchingReference(source, target)) { return true; } } return false; } function isDiscriminantProperty(type: Type | undefined, name: __String) { if (type && type.flags & TypeFlags.Union) { const prop = getUnionOrIntersectionProperty(type as UnionType, name); if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty if ((prop as TransientSymbol).links.isDiscriminantProperty === undefined) { (prop as TransientSymbol).links.isDiscriminantProperty = ((prop as TransientSymbol).links.checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && !isGenericType(getTypeOfSymbol(prop)); } return !!(prop as TransientSymbol).links.isDiscriminantProperty; } } return false; } function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined { let result: Symbol[] | undefined; for (const sourceProperty of sourceProperties) { if (isDiscriminantProperty(target, sourceProperty.escapedName)) { if (result) { result.push(sourceProperty); continue; } result = [sourceProperty]; } } return result; } // Given a set of constituent types and a property name, create and return a map keyed by the literal // types of the property by that name in each constituent type. No map is returned if some key property // has a non-literal type or if less than 10 or less than 50% of the constituents have a unique key. // Entries with duplicate keys have unknownType as the value. function mapTypesByKeyProperty(types: Type[], name: __String) { const map = new Map(); let count = 0; for (const type of types) { if (type.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { const discriminant = getTypeOfPropertyOfType(type, name); if (discriminant) { if (!isLiteralType(discriminant)) { return undefined; } let duplicate = false; forEachType(discriminant, t => { const id = getTypeId(getRegularTypeOfLiteralType(t)); const existing = map.get(id); if (!existing) { map.set(id, type); } else if (existing !== unknownType) { map.set(id, unknownType); duplicate = true; } }); if (!duplicate) count++; } } } return count >= 10 && count * 2 >= types.length ? map : undefined; } // Return the name of a discriminant property for which it was possible and feasible to construct a map of // constituent types keyed by the literal types of the property by that name in each constituent type. function getKeyPropertyName(unionType: UnionType): __String | undefined { const types = unionType.types; // We only construct maps for unions with many non-primitive constituents. if ( types.length !!(t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive))) t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) ? forEach(getPropertiesOfType(t), p => isUnitType(getTypeOfSymbol(p)) ? p.escapedName : undefined) : undefined); const mapByKeyProperty = keyPropertyName && mapTypesByKeyProperty(types, keyPropertyName); unionType.keyPropertyName = mapByKeyProperty ? keyPropertyName : "" as __String; unionType.constituentMap = mapByKeyProperty; } return (unionType.keyPropertyName as string).length ? unionType.keyPropertyName : undefined; } // Given a union type for which getKeyPropertyName returned a non-undefined result, return the constituent // that corresponds to the given key type for that property name. function getConstituentTypeForKeyType(unionType: UnionType, keyType: Type) { const result = unionType.constituentMap?.get(getTypeId(getRegularTypeOfLiteralType(keyType))); return result !== unknownType ? result : undefined; } function getMatchingUnionConstituentForType(unionType: UnionType, type: Type) { const keyPropertyName = getKeyPropertyName(unionType); const propType = keyPropertyName && getTypeOfPropertyOfType(type, keyPropertyName); return propType && getConstituentTypeForKeyType(unionType, propType); } function getMatchingUnionConstituentForObjectLiteral(unionType: UnionType, node: ObjectLiteralExpression) { const keyPropertyName = getKeyPropertyName(unionType); const propNode = keyPropertyName && find(node.properties, p => p.symbol && p.kind === SyntaxKind.PropertyAssignment && p.symbol.escapedName === keyPropertyName && isPossiblyDiscriminantValue(p.initializer)); const propType = propNode && getContextFreeTypeOfExpression((propNode as PropertyAssignment).initializer); return propType && getConstituentTypeForKeyType(unionType, propType); } function isOrContainsMatchingReference(source: Node, target: Node) { return isMatchingReference(source, target) || containsMatchingReference(source, target); } function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) { if (expression.arguments) { for (const argument of expression.arguments) { if (isOrContainsMatchingReference(reference, argument) || optionalChainContainsReference(argument, reference)) { return true; } } } if ( expression.expression.kind === SyntaxKind.PropertyAccessExpression && isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression) ) { return true; } return false; } function getFlowNodeId(flow: FlowNode): number { if (flow.id typeMaybeAssignableTo(assignedType, t)); // Ensure that we narrow to fresh types if the assignment is a fresh boolean literal type. const reducedType = assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType) ? mapType(filteredType, getFreshTypeOfLiteralType) : filteredType; // Our crude heuristic produces an invalid result in some cases: see GH#26130. // For now, when that happens, we give up and don't narrow at all. (This also // means we'll never narrow for erroneous assignments where the assigned type // is not assignable to the declared type.) return isTypeAssignableTo(assignedType, reducedType) ? reducedType : declaredType; } function isFunctionObjectType(type: ObjectType): boolean { if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { return false; } // We do a quick check for a "bind" property before performing the more expensive subtype // check. This gives us a quicker out in the common case where an object type is not a function. const resolved = resolveStructuredTypeMembers(type); return !!(resolved.callSignatures.length || resolved.constructSignatures.length || resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType)); } function getTypeFacts(type: Type, mask: TypeFacts): TypeFacts { return getTypeFactsWorker(type, mask) & mask; } function hasTypeFacts(type: Type, mask: TypeFacts): boolean { return getTypeFacts(type, mask) !== 0; } function getTypeFactsWorker(type: Type, callerOnlyNeeds: TypeFacts): TypeFacts { if (type.flags & (TypeFlags.Intersection | TypeFlags.Instantiable)) { type = getBaseConstraintOfType(type) || unknownType; } const flags = type.flags; if (flags & (TypeFlags.String | TypeFlags.StringMapping)) { return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts; } if (flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral)) { const isEmpty = flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === ""; return strictNullChecks ? isEmpty ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts : isEmpty ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts; } if (flags & (TypeFlags.Number | TypeFlags.Enum)) { return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts; } if (flags & TypeFlags.NumberLiteral) { const isZero = (type as NumberLiteralType).value === 0; return strictNullChecks ? isZero ? TypeFacts.ZeroNumberStrictFacts : TypeFacts.NonZeroNumberStrictFacts : isZero ? TypeFacts.ZeroNumberFacts : TypeFacts.NonZeroNumberFacts; } if (flags & TypeFlags.BigInt) { return strictNullChecks ? TypeFacts.BigIntStrictFacts : TypeFacts.BigIntFacts; } if (flags & TypeFlags.BigIntLiteral) { const isZero = isZeroBigInt(type as BigIntLiteralType); return strictNullChecks ? isZero ? TypeFacts.ZeroBigIntStrictFacts : TypeFacts.NonZeroBigIntStrictFacts : isZero ? TypeFacts.ZeroBigIntFacts : TypeFacts.NonZeroBigIntFacts; } if (flags & TypeFlags.Boolean) { return strictNullChecks ? TypeFacts.BooleanStrictFacts : TypeFacts.BooleanFacts; } if (flags & TypeFlags.BooleanLike) { return strictNullChecks ? (type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts : (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; } if (flags & TypeFlags.Object) { const possibleFacts = strictNullChecks ? TypeFacts.EmptyObjectStrictFacts | TypeFacts.FunctionStrictFacts | TypeFacts.ObjectStrictFacts : TypeFacts.EmptyObjectFacts | TypeFacts.FunctionFacts | TypeFacts.ObjectFacts; if ((callerOnlyNeeds & possibleFacts) === 0) { // If the caller doesn't care about any of the facts that we could possibly produce, // return zero so we can skip resolving members. return 0; } return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : isFunctionObjectType(type as ObjectType) ? strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } if (flags & TypeFlags.Void) { return TypeFacts.VoidFacts; } if (flags & TypeFlags.Undefined) { return TypeFacts.UndefinedFacts; } if (flags & TypeFlags.Null) { return TypeFacts.NullFacts; } if (flags & TypeFlags.ESSymbolLike) { return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts; } if (flags & TypeFlags.NonPrimitive) { return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } if (flags & TypeFlags.Never) { return TypeFacts.None; } if (flags & TypeFlags.Union) { return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFactsWorker(t, callerOnlyNeeds), TypeFacts.None); } if (flags & TypeFlags.Intersection) { return getIntersectionTypeFacts(type as IntersectionType, callerOnlyNeeds); } return TypeFacts.UnknownFacts; } function getIntersectionTypeFacts(type: IntersectionType, callerOnlyNeeds: TypeFacts): TypeFacts { // When an intersection contains a primitive type we ignore object type constituents as they are // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. const ignoreObjects = maybeTypeOfKind(type, TypeFlags.Primitive); // When computing the type facts of an intersection type, certain type facts are computed as `and` // and others are computed as `or`. let oredFacts = TypeFacts.None; let andedFacts = TypeFacts.All; for (const t of type.types) { if (!(ignoreObjects && t.flags & TypeFlags.Object)) { const f = getTypeFactsWorker(t, callerOnlyNeeds); oredFacts |= f; andedFacts &= f; } } return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask; } function getTypeWithFacts(type: Type, include: TypeFacts) { return filterType(type, t => hasTypeFacts(t, include)); } // This function is similar to getTypeWithFacts, except that in strictNullChecks mode it replaces type // unknown with the union {} | null | undefined (and reduces that accordingly), and it intersects remaining // instantiable types with {}, {} | null, or {} | undefined in order to remove null and/or undefined. function getAdjustedTypeWithFacts(type: Type, facts: TypeFacts) { const reduced = recombineUnknownType(getTypeWithFacts(strictNullChecks && type.flags & TypeFlags.Unknown ? unknownUnionType : type, facts)); if (strictNullChecks) { switch (facts) { case TypeFacts.NEUndefined: return removeNullableByIntersection(reduced, TypeFacts.EQUndefined, TypeFacts.EQNull, TypeFacts.IsNull, nullType); case TypeFacts.NENull: return removeNullableByIntersection(reduced, TypeFacts.EQNull, TypeFacts.EQUndefined, TypeFacts.IsUndefined, undefinedType); case TypeFacts.NEUndefinedOrNull: case TypeFacts.Truthy: return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefinedOrNull) ? getGlobalNonNullableTypeInstantiation(t) : t); } } return reduced; } function removeNullableByIntersection(type: Type, targetFacts: TypeFacts, otherFacts: TypeFacts, otherIncludesFacts: TypeFacts, otherType: Type) { const facts = getTypeFacts(type, TypeFacts.EQUndefined | TypeFacts.EQNull | TypeFacts.IsUndefined | TypeFacts.IsNull); // Simply return the type if it never compares equal to the target nullable. if (!(facts & targetFacts)) { return type; } // By default we intersect with a union of {} and the opposite nullable. const emptyAndOtherUnion = getUnionType([emptyObjectType, otherType]); // For each constituent type that can compare equal to the target nullable, intersect with the above union // if the type doesn't already include the opppsite nullable and the constituent can compare equal to the // opposite nullable; otherwise, just intersect with {}. return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, !(facts & otherIncludesFacts) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t); } function recombineUnknownType(type: Type) { return type === unknownUnionType ? unknownType : type; } function getTypeWithDefault(type: Type, defaultExpression: Expression) { return defaultExpression ? getUnionType([getNonUndefinedType(type), getTypeOfExpression(defaultExpression)]) : type; } function getTypeOfDestructuredProperty(type: Type, name: PropertyName) { const nameType = getLiteralTypeFromPropertyName(name); if (!isTypeUsableAsPropertyName(nameType)) return errorType; const text = getPropertyNameFromType(nameType); return getTypeOfPropertyOfType(type, text) || includeUndefinedInIndexSignature(getApplicableIndexInfoForName(type, text)?.type) || errorType; } function getTypeOfDestructuredArrayElement(type: Type, index: number) { return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || includeUndefinedInIndexSignature(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined)) || errorType; } function includeUndefinedInIndexSignature(type: Type | undefined): Type | undefined { if (!type) return type; return compilerOptions.noUncheckedIndexedAccess ? getUnionType([type, missingType]) : type; } function getTypeOfDestructuredSpreadExpression(type: Type) { return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType); } function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type { const isDestructuringDefaultAssignment = node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) || node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent); return isDestructuringDefaultAssignment ? getTypeWithDefault(getAssignedType(node), node.right) : getTypeOfExpression(node.right); } function isDestructuringAssignmentTarget(parent: Node) { return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent || parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent; } function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type { return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); } function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type { return getTypeOfDestructuredSpreadExpression(getAssignedType(node.parent as ArrayLiteralExpression)); } function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment): Type { return getTypeOfDestructuredProperty(getAssignedType(node.parent), node.name); } function getAssignedTypeOfShorthandPropertyAssignment(node: ShorthandPropertyAssignment): Type { return getTypeWithDefault(getAssignedTypeOfPropertyAssignment(node), node.objectAssignmentInitializer!); } function getAssignedType(node: Expression): Type { const { parent } = node; switch (parent.kind) { case SyntaxKind.ForInStatement: return stringType; case SyntaxKind.ForOfStatement: return checkRightHandSideOfForOf(parent as ForOfStatement) || errorType; case SyntaxKind.BinaryExpression: return getAssignedTypeOfBinaryExpression(parent as BinaryExpression); case SyntaxKind.DeleteExpression: return undefinedType; case SyntaxKind.ArrayLiteralExpression: return getAssignedTypeOfArrayLiteralElement(parent as ArrayLiteralExpression, node); case SyntaxKind.SpreadElement: return getAssignedTypeOfSpreadExpression(parent as SpreadElement); case SyntaxKind.PropertyAssignment: return getAssignedTypeOfPropertyAssignment(parent as PropertyAssignment); case SyntaxKind.ShorthandPropertyAssignment: return getAssignedTypeOfShorthandPropertyAssignment(parent as ShorthandPropertyAssignment); } return errorType; } function getInitialTypeOfBindingElement(node: BindingElement): Type { const pattern = node.parent; const parentType = getInitialType(pattern.parent as VariableDeclaration | BindingElement); const type = pattern.kind === SyntaxKind.ObjectBindingPattern ? getTypeOfDestructuredProperty(parentType, node.propertyName || node.name as Identifier) : !node.dotDotDotToken ? getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) : getTypeOfDestructuredSpreadExpression(parentType); return getTypeWithDefault(type, node.initializer!); } function getTypeOfInitializer(node: Expression) { // Return the cached type if one is available. If the type of the variable was inferred // from its initializer, we'll already have cached the type. Otherwise we compute it now // without caching such that transient types are reflected. const links = getNodeLinks(node); return links.resolvedType || getTypeOfExpression(node); } function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { if (node.initializer) { return getTypeOfInitializer(node.initializer); } if (node.parent.parent.kind === SyntaxKind.ForInStatement) { return stringType; } if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { return checkRightHandSideOfForOf(node.parent.parent) || errorType; } return errorType; } function getInitialType(node: VariableDeclaration | BindingElement) { return node.kind === SyntaxKind.VariableDeclaration ? getInitialTypeOfVariableDeclaration(node) : getInitialTypeOfBindingElement(node); } function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { return node.kind === SyntaxKind.VariableDeclaration && (node as VariableDeclaration).initializer && isEmptyArrayLiteral((node as VariableDeclaration).initializer!) || node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && isEmptyArrayLiteral((node.parent as BinaryExpression).right); } function getReferenceCandidate(node: Expression): Expression { switch (node.kind) { case SyntaxKind.ParenthesizedExpression: return getReferenceCandidate((node as ParenthesizedExpression).expression); case SyntaxKind.BinaryExpression: switch ((node as BinaryExpression).operatorToken.kind) { case SyntaxKind.EqualsToken: case SyntaxKind.BarBarEqualsToken: case SyntaxKind.AmpersandAmpersandEqualsToken: case SyntaxKind.QuestionQuestionEqualsToken: return getReferenceCandidate((node as BinaryExpression).left); case SyntaxKind.CommaToken: return getReferenceCandidate((node as BinaryExpression).right); } } return node; } function getReferenceRoot(node: Node): Node { const { parent } = node; return parent.kind === SyntaxKind.ParenthesizedExpression || parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && (parent as BinaryExpression).left === node || parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken && (parent as BinaryExpression).right === node ? getReferenceRoot(parent) : node; } function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { if (clause.kind === SyntaxKind.CaseClause) { return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); } return neverType; } function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] { const links = getNodeLinks(switchStatement); if (!links.switchTypes) { links.switchTypes = []; for (const clause of switchStatement.caseBlock.clauses) { links.switchTypes.push(getTypeOfSwitchClause(clause)); } } return links.switchTypes; } // Get the type names from all cases in a switch on `typeof`. The default clause and/or duplicate type names are // represented as undefined. Return undefined if one or more case clause expressions are not string literals. function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] | undefined { if (some(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.CaseClause && !isStringLiteralLike(clause.expression))) { return undefined; } const witnesses: (string | undefined)[] = []; for (const clause of switchStatement.caseBlock.clauses) { const text = clause.kind === SyntaxKind.CaseClause ? (clause.expression as StringLiteralLike).text : undefined; witnesses.push(text && !contains(witnesses, text) ? text : undefined); } return witnesses; } function eachTypeContainedIn(source: Type, types: Type[]) { return source.flags & TypeFlags.Union ? !forEach((source as UnionType).types, t => !contains(types, t)) : contains(types, source); } function isTypeSubsetOf(source: Type, target: Type) { return !!(source === target || source.flags & TypeFlags.Never || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType)); } function isTypeSubsetOfUnion(source: Type, target: UnionType) { if (source.flags & TypeFlags.Union) { for (const t of (source as UnionType).types) { if (!containsType(target.types, t)) { return false; } } return true; } if (source.flags & TypeFlags.EnumLike && getBaseTypeOfEnumLikeType(source as LiteralType) === target) { return true; } return containsType(target.types, source); } function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined { return type.flags & TypeFlags.Union ? forEach((type as UnionType).types, f) : f(type); } function someType(type: Type, f: (t: Type) => boolean): boolean { return type.flags & TypeFlags.Union ? some((type as UnionType).types, f) : f(type); } function everyType(type: Type, f: (t: Type) => boolean): boolean { return type.flags & TypeFlags.Union ? every((type as UnionType).types, f) : f(type); } function everyContainedType(type: Type, f: (t: Type) => boolean): boolean { return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type); } function filterType(type: Type, f: (t: Type) => boolean): Type { if (type.flags & TypeFlags.Union) { const types = (type as UnionType).types; const filtered = filter(types, f); if (filtered === types) { return type; } const origin = (type as UnionType).origin; let newOrigin: Type | undefined; if (origin && origin.flags & TypeFlags.Union) { // If the origin type is a (denormalized) union type, filter its non-union constituents. If that ends // up removing a smaller number of types than in the normalized constituent set (meaning some of the // filtered types are within nested unions in the origin), then we can't construct a new origin type. // Otherwise, if we have exactly one type left in the origin set, return that as the filtered type. // Otherwise, construct a new filtered origin type. const originTypes = (origin as UnionType).types; const originFiltered = filter(originTypes, t => !!(t.flags & TypeFlags.Union) || f(t)); if (originTypes.length - originFiltered.length === types.length - filtered.length) { if (originFiltered.length === 1) { return originFiltered[0]; } newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered); } } // filtering could remove intersections so `ContainsIntersections` might be forwarded "incorrectly" // it is purely an optimization hint so there is no harm in accidentally forwarding it return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags & (ObjectFlags.PrimitiveUnion | ObjectFlags.ContainsIntersections), /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); } return type.flags & TypeFlags.Never || f(type) ? type : neverType; } function removeType(type: Type, targetType: Type) { return filterType(type, t => t !== targetType); } function countTypes(type: Type) { return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; } // Apply a mapping function to a type and return the resulting type. If the source type // is a union type, the mapping function is applied to each constituent type and a union // of the resulting types is returned. function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { if (type.flags & TypeFlags.Never) { return type; } if (!(type.flags & TypeFlags.Union)) { return mapper(type); } const origin = (type as UnionType).origin; const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types; let mappedTypes: Type[] | undefined; let changed = false; for (const t of types) { const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions) : mapper(t); changed ||= t !== mapped; if (mapped) { if (!mappedTypes) { mappedTypes = [mapped]; } else { mappedTypes.push(mapped); } } } return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; } function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { return type.flags & TypeFlags.Union && aliasSymbol ? getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments) : mapType(type, mapper); } function extractTypesOfKind(type: Type, kind: TypeFlags) { return filterType(type, t => (t.flags & kind) !== 0); } // Return a new type in which occurrences of the string, number and bigint primitives and placeholder template // literal types in typeWithPrimitives have been replaced with occurrences of compatible and more specific types // from typeWithLiterals. This is essentially a limited form of intersection between the two types. We avoid a // true intersection because it is more costly and, when applied to union types, generates a large number of // types we don't actually care about. function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) { if ( maybeTypeOfKind(typeWithPrimitives, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.Number | TypeFlags.BigInt) && maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.NumberLiteral | TypeFlags.BigIntLiteral) ) { return mapType(typeWithPrimitives, t => t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) : isPatternLiteralType(t) && !maybeTypeOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? extractTypesOfKind(typeWithLiterals, TypeFlags.StringLiteral) : t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) : t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t); } return typeWithPrimitives; } function isIncomplete(flowType: FlowType) { return flowType.flags === 0; } function getTypeFromFlowType(flowType: FlowType) { return flowType.flags === 0 ? flowType.type : flowType as Type; } function createFlowType(type: Type, incomplete: boolean): FlowType { return incomplete ? { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type } : type; } // An evolving array type tracks the element types that have so far been seen in an // 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving // array types are ultimately converted into manifest array types (using getFinalArrayType) // and never escape the getFlowTypeOfReference function. function createEvolvingArrayType(elementType: Type): EvolvingArrayType { const result = createObjectType(ObjectFlags.EvolvingArray) as EvolvingArrayType; result.elementType = elementType; return result; } function getEvolvingArrayType(elementType: Type): EvolvingArrayType { return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType)); } // When adding evolving array element types we do not perform subtype reduction. Instead, // we defer subtype reduction until the evolving array type is finalized into a manifest // array type. function addEvolvingArrayElementType(evolvingArrayType: EvolvingArrayType, node: Expression): EvolvingArrayType { const elementType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(getContextFreeTypeOfExpression(node))); return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType])); } function createFinalArrayType(elementType: Type) { return elementType.flags & TypeFlags.Never ? autoArrayType : createArrayType( elementType.flags & TypeFlags.Union ? getUnionType((elementType as UnionType).types, UnionReduction.Subtype) : elementType, ); } // We perform subtype reduction upon obtaining the final array type from an evolving array type. function getFinalArrayType(evolvingArrayType: EvolvingArrayType): Type { return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType)); } function finalizeEvolvingArrayType(type: Type): Type { return getObjectFlags(type) & ObjectFlags.EvolvingArray ? getFinalArrayType(type as EvolvingArrayType) : type; } function getElementTypeOfEvolvingArrayType(type: Type) { return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (type as EvolvingArrayType).elementType : neverType; } function isEvolvingArrayTypeList(types: Type[]) { let hasEvolvingArrayType = false; for (const t of types) { if (!(t.flags & TypeFlags.Never)) { if (!(getObjectFlags(t) & ObjectFlags.EvolvingArray)) { return false; } hasEvolvingArrayType = true; } } return hasEvolvingArrayType; } // Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or // 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type. function isEvolvingArrayOperationTarget(node: Node) { const root = getReferenceRoot(node); const parent = root.parent; const isLengthPushOrUnshift = isPropertyAccessExpression(parent) && ( parent.name.escapedText === "length" || parent.parent.kind === SyntaxKind.CallExpression && isIdentifier(parent.name) && isPushOrUnshiftIdentifier(parent.name) ); const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === root && parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && (parent.parent as BinaryExpression).left === parent && !isAssignmentTarget(parent.parent) && isTypeAssignableToKind(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression), TypeFlags.NumberLike); return isLengthPushOrUnshift || isElementAssignment; } function isDeclarationWithExplicitTypeAnnotation(node: Declaration) { return (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isParameter(node)) && !!(getEffectiveTypeAnnotationNode(node) || isInJSFile(node) && hasInitializer(node) && node.initializer && isFunctionExpressionOrArrowFunction(node.initializer) && getEffectiveReturnTypeNode(node.initializer)); } function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) { symbol = resolveSymbol(symbol); if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) { return getTypeOfSymbol(symbol); } if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { if (getCheckFlags(symbol) & CheckFlags.Mapped) { const origin = (symbol as MappedSymbol).links.syntheticOrigin; if (origin && getExplicitTypeOfSymbol(origin)) { return getTypeOfSymbol(symbol); } } const declaration = symbol.valueDeclaration; if (declaration) { if (isDeclarationWithExplicitTypeAnnotation(declaration)) { return getTypeOfSymbol(symbol); } if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { const statement = declaration.parent.parent; const expressionType = getTypeOfDottedName(statement.expression, /*diagnostic*/ undefined); if (expressionType) { const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; return checkIteratedTypeOrElementType(use, expressionType, undefinedType, /*errorNode*/ undefined); } } if (diagnostic) { addRelatedInfo(diagnostic, createDiagnosticForNode(declaration, Diagnostics._0_needs_an_explicit_type_annotation, symbolToString(symbol))); } } } } // We require the dotted function name in an assertion expression to be comprised of identifiers // that reference function, method, class or value module symbols; or variable, property or // parameter symbols with declarations that have explicit type annotations. Such references are // resolvable with no possibility of triggering circularities in control flow analysis. function getTypeOfDottedName(node: Expression, diagnostic: Diagnostic | undefined): Type | undefined { if (!(node.flags & NodeFlags.InWithStatement)) { switch (node.kind) { case SyntaxKind.Identifier: const symbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(node as Identifier)); return getExplicitTypeOfSymbol(symbol, diagnostic); case SyntaxKind.ThisKeyword: return getExplicitThisType(node); case SyntaxKind.SuperKeyword: return checkSuperExpression(node); case SyntaxKind.PropertyAccessExpression: { const type = getTypeOfDottedName((node as PropertyAccessExpression).expression, diagnostic); if (type) { const name = (node as PropertyAccessExpression).name; let prop: Symbol | undefined; if (isPrivateIdentifier(name)) { if (!type.symbol) { return undefined; } prop = getPropertyOfType(type, getSymbolNameForPrivateIdentifier(type.symbol, name.escapedText)); } else { prop = getPropertyOfType(type, name.escapedText); } return prop && getExplicitTypeOfSymbol(prop, diagnostic); } return undefined; } case SyntaxKind.ParenthesizedExpression: return getTypeOfDottedName((node as ParenthesizedExpression).expression, diagnostic); } } } function getEffectsSignature(node: CallExpression | InstanceofExpression) { const links = getNodeLinks(node); let signature = links.effectsSignature; if (signature === undefined) { // A call expression parented by an expression statement is a potential assertion. Other call // expressions are potential type predicate function calls. In order to avoid triggering // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call // target expression of an assertion. let funcType: Type | undefined; if (isBinaryExpression(node)) { const rightType = checkNonNullExpression(node.right); funcType = getSymbolHasInstanceMethodOfObjectType(rightType); } else if (node.parent.kind === SyntaxKind.ExpressionStatement) { funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined); } else if (node.expression.kind !== SyntaxKind.SuperKeyword) { if (isOptionalChain(node)) { funcType = checkNonNullType( getOptionalExpressionType(checkExpression(node.expression), node.expression), node.expression, ); } else { funcType = checkNonNullExpression(node.expression); } } const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) : undefined; signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature; } return signature === unknownSignature ? undefined : signature; } function hasTypePredicateOrNeverReturnType(signature: Signature) { return !!(getTypePredicateOfSignature(signature) || signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never); } function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) { if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { return callExpression.arguments[predicate.parameterIndex]; } const invokedExpression = skipParentheses(callExpression.expression); return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined; } function reportFlowControlError(node: Node) { const block = findAncestor(node, isFunctionOrModuleBlock) as Block | ModuleBlock | SourceFile; const sourceFile = getSourceFileOfNode(node); const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos); diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis)); } function isReachableFlowNode(flow: FlowNode) { const result = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ false); lastFlowNode = flow; lastFlowNodeReachable = result; return result; } function isFalseExpression(expr: Expression): boolean { const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && ( (node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node as BinaryExpression).left) || isFalseExpression((node as BinaryExpression).right)) || (node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node as BinaryExpression).left) && isFalseExpression((node as BinaryExpression).right) ); } function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { while (true) { if (flow === lastFlowNode) { return lastFlowNodeReachable; } const flags = flow.flags; if (flags & FlowFlags.Shared) { if (!noCacheCheck) { const id = getFlowNodeId(flow); const reachable = flowNodeReachable[id]; return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ true)); } noCacheCheck = false; } if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) { flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation).antecedent; } else if (flags & FlowFlags.Call) { const signature = getEffectsSignature((flow as FlowCall).node); if (signature) { const predicate = getTypePredicateOfSignature(signature); if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier && !predicate.type) { const predicateArgument = (flow as FlowCall).node.arguments[predicate.parameterIndex]; if (predicateArgument && isFalseExpression(predicateArgument)) { return false; } } if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { return false; } } flow = (flow as FlowCall).antecedent; } else if (flags & FlowFlags.BranchLabel) { // A branching point is reachable if any branch is reachable. return some((flow as FlowLabel).antecedent, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); } else if (flags & FlowFlags.LoopLabel) { const antecedents = (flow as FlowLabel).antecedent; if (antecedents === undefined || antecedents.length === 0) { return false; } // A loop is reachable if the control flow path that leads to the top is reachable. flow = antecedents[0]; } else if (flags & FlowFlags.SwitchClause) { // The control flow path representing an unmatched value in a switch statement with // no default clause is unreachable if the switch statement is exhaustive. const data = (flow as FlowSwitchClause).node; if (data.clauseStart === data.clauseEnd && isExhaustiveSwitchStatement(data.switchStatement)) { return false; } flow = (flow as FlowSwitchClause).antecedent; } else if (flags & FlowFlags.ReduceLabel) { // Cache is unreliable once we start adjusting labels lastFlowNode = undefined; const target = (flow as FlowReduceLabel).node.target; const saveAntecedents = target.antecedent; target.antecedent = (flow as FlowReduceLabel).node.antecedents; const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); target.antecedent = saveAntecedents; return result; } else { return !(flags & FlowFlags.Unreachable); } } } // Return true if the given flow node is preceded by a 'super(...)' call in every possible code path // leading to the node. function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean { while (true) { const flags = flow.flags; if (flags & FlowFlags.Shared) { if (!noCacheCheck) { const id = getFlowNodeId(flow); const postSuper = flowNodePostSuper[id]; return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true)); } noCacheCheck = false; } if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) { flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation | FlowSwitchClause).antecedent; } else if (flags & FlowFlags.Call) { if ((flow as FlowCall).node.expression.kind === SyntaxKind.SuperKeyword) { return true; } flow = (flow as FlowCall).antecedent; } else if (flags & FlowFlags.BranchLabel) { // A branching point is post-super if every branch is post-super. return every((flow as FlowLabel).antecedent, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); } else if (flags & FlowFlags.LoopLabel) { // A loop is post-super if the control flow path that leads to the top is post-super. flow = (flow as FlowLabel).antecedent![0]; } else if (flags & FlowFlags.ReduceLabel) { const target = (flow as FlowReduceLabel).node.target; const saveAntecedents = target.antecedent; target.antecedent = (flow as FlowReduceLabel).node.antecedents; const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); target.antecedent = saveAntecedents; return result; } else { // Unreachable nodes are considered post-super to silence errors return !!(flags & FlowFlags.Unreachable); } } } function isConstantReference(node: Node): boolean { switch (node.kind) { case SyntaxKind.ThisKeyword: return true; case SyntaxKind.Identifier: if (!isThisInTypeQuery(node)) { const symbol = getResolvedSymbol(node as Identifier); return isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol) || !!symbol.valueDeclaration && isFunctionExpression(symbol.valueDeclaration); } break; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: // The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here. return isConstantReference((node as AccessExpression).expression) && isReadonlySymbol(getNodeLinks(node).resolvedSymbol || unknownSymbol); case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: const rootDeclaration = getRootDeclaration(node.parent); return isParameter(rootDeclaration) || isCatchClauseVariableDeclaration(rootDeclaration) ? !isSomeSymbolAssigned(rootDeclaration) : isVariableDeclaration(rootDeclaration) && isVarConstLike(rootDeclaration); } return false; } function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = tryCast(reference, canHaveFlowNode)?.flowNode) { let key: string | undefined; let isKeySet = false; let flowDepth = 0; if (flowAnalysisDisabled) { return errorType; } if (!flowNode) { return declaredType; } flowInvocationCount++; const sharedFlowStart = sharedFlowCount; const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode)); sharedFlowCount = sharedFlowStart; // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations // on empty arrays are possible without implicit any errors and new element types can be inferred without // type mismatch errors. const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType); if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && !(resultType.flags & TypeFlags.Never) && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { return declaredType; } return resultType; function getOrSetCacheKey() { if (isKeySet) { return key; } isKeySet = true; return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); } function getTypeAtFlowNode(flow: FlowNode): FlowType { if (flowDepth === 2000) { // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error // and disable further control flow analysis in the containing function or module body. tracing?.instant(tracing.Phase.CheckTypes, "getTypeAtFlowNode_DepthLimit", { flowId: flow.id }); flowAnalysisDisabled = true; reportFlowControlError(reference); return errorType; } flowDepth++; let sharedFlow: FlowNode | undefined; while (true) { const flags = flow.flags; if (flags & FlowFlags.Shared) { // We cache results of flow type resolution for shared nodes that were previously visited in // the same getFlowTypeOfReference invocation. A node is considered shared when it is the // antecedent of more than one node. for (let i = sharedFlowStart; i = 0 && predicate.parameterIndex !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); } else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); } } const access = getDiscriminantPropertyAccess(expr, type); if (access) { type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.node); } } return createFlowType(type, isIncomplete(flowType)); } function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType { const antecedentTypes: Type[] = []; let subtypeReduction = false; let seenIncomplete = false; let bypassFlow: FlowSwitchClause | undefined; for (const antecedent of flow.antecedent!) { if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).node.clauseStart === (antecedent as FlowSwitchClause).node.clauseEnd) { // The antecedent is the bypass branch of a potentially exhaustive switch statement. bypassFlow = antecedent as FlowSwitchClause; continue; } const flowType = getTypeAtFlowNode(antecedent); const type = getTypeFromFlowType(flowType); // If the type at a particular antecedent path is the declared type and the // reference is known to always be assigned (i.e. when declared and initial types // are the same), there is no reason to process more antecedents since the only // possible outcome is subtypes that will be removed in the final union type anyway. if (type === declaredType && declaredType === initialType) { return type; } pushIfUnique(antecedentTypes, type); // If an antecedent type is not a subset of the declared type, we need to perform // subtype reduction. This happens when a "foreign" type is injected into the control // flow using the instanceof operator or a user defined type predicate. if (!isTypeSubsetOf(type, initialType)) { subtypeReduction = true; } if (isIncomplete(flowType)) { seenIncomplete = true; } } if (bypassFlow) { const flowType = getTypeAtFlowNode(bypassFlow); const type = getTypeFromFlowType(flowType); // If the bypass flow contributes a type we haven't seen yet and the switch statement // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase // the risk of circularities, we only want to perform them when they make a difference. if (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node.switchStatement)) { if (type === declaredType && declaredType === initialType) { return type; } antecedentTypes.push(type); if (!isTypeSubsetOf(type, initialType)) { subtypeReduction = true; } if (isIncomplete(flowType)) { seenIncomplete = true; } } } return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); } function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { // If we have previously computed the control flow type for the reference at // this flow loop junction, return the cached type. const id = getFlowNodeId(flow); const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new Map()); const key = getOrSetCacheKey(); if (!key) { // No cache key is generated when binding patterns are in unnarrowable situations return declaredType; } const cached = cache.get(key); if (cached) { return cached; } // If this flow loop junction and reference are already being processed, return // the union of the types computed for each branch so far, marked as incomplete. // It is possible to see an empty array in cases where loops are nested and the // back edge of the outer loop reaches an inner loop that is already being analyzed. // In such cases we restart the analysis of the inner loop, which will then see // a non-empty in-process array for the outer loop and eventually terminate because // the first antecedent of a loop junction is always the non-looping control flow // path that leads to the top. for (let i = flowLoopStart; i Type): Type { const propName = getAccessedPropertyName(access); if (propName === undefined) { return type; } const optionalChain = isOptionalChain(access); const removeNullable = strictNullChecks && (optionalChain || isNonNullAccess(access)) && maybeTypeOfKind(type, TypeFlags.Nullable); let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName); if (!propType) { return type; } propType = removeNullable && optionalChain ? getOptionalType(propType) : propType; const narrowedPropType = narrowType(propType); return filterType(type, t => { const discriminantType = getTypeOfPropertyOrIndexSignatureOfType(t, propName) || unknownType; return !(discriminantType.flags & TypeFlags.Never) && !(narrowedPropType.flags & TypeFlags.Never) && areTypesComparable(narrowedPropType, discriminantType); }); } function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) { const keyPropertyName = getKeyPropertyName(type as UnionType); if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) { const candidate = getConstituentTypeForKeyType(type as UnionType, getTypeOfExpression(value)); if (candidate) { return operator === (assumeTrue ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken) ? candidate : isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(type, candidate) : type; } } } return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); } function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, data: FlowSwitchClauseData) { if (data.clauseStart getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); if (candidate !== unknownType) { return candidate; } } return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, data)); } function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { if (isMatchingReference(reference, expr)) { return getAdjustedTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); } if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } const access = getDiscriminantPropertyAccess(expr, type); if (access) { return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); } return type; } function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { const prop = getPropertyOfType(type, propName); return prop ? !!(prop.flags & SymbolFlags.Optional || getCheckFlags(prop) & CheckFlags.Partial) || assumeTrue : !!getApplicableIndexInfoForName(type, propName) || !assumeTrue; } function narrowTypeByInKeyword(type: Type, nameType: StringLiteralType | NumberLiteralType | UniqueESSymbolType, assumeTrue: boolean) { const name = getPropertyNameFromType(nameType); const isKnownProperty = someType(type, t => isTypePresencePossible(t, name, /*assumeTrue*/ true)); if (isKnownProperty) { // If the check is for a known property (i.e. a property declared in some constituent of // the target type), we filter the target type by presence of absence of the property. return filterType(type, t => isTypePresencePossible(t, name, assumeTrue)); } if (assumeTrue) { // If the check is for an unknown property, we intersect the target type with `Record`, // where X is the name of the property. const recordSymbol = getGlobalRecordSymbol(); if (recordSymbol) { return getIntersectionType([type, getTypeAliasInstantiation(recordSymbol, [nameType, unknownType])]); } } return type; } function narrowTypeByBooleanComparison(type: Type, expr: Expression, bool: BooleanLiteral, operator: BinaryOperator, assumeTrue: boolean): Type { assumeTrue = (assumeTrue !== (bool.kind === SyntaxKind.TrueKeyword)) !== (operator !== SyntaxKind.ExclamationEqualsEqualsToken && operator !== SyntaxKind.ExclamationEqualsToken); return narrowType(type, expr, assumeTrue); } function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsToken: case SyntaxKind.BarBarEqualsToken: case SyntaxKind.AmpersandAmpersandEqualsToken: case SyntaxKind.QuestionQuestionEqualsToken: return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue); case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: const operator = expr.operatorToken.kind; const left = getReferenceCandidate(expr.left); const right = getReferenceCandidate(expr.right); if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) { return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue); } if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue); } if (isMatchingReference(reference, left)) { return narrowTypeByEquality(type, operator, right, assumeTrue); } if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } if (strictNullChecks) { if (optionalChainContainsReference(left, reference)) { type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); } else if (optionalChainContainsReference(right, reference)) { type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue); } } const leftAccess = getDiscriminantPropertyAccess(left, type); if (leftAccess) { return narrowTypeByDiscriminantProperty(type, leftAccess, operator, right, assumeTrue); } const rightAccess = getDiscriminantPropertyAccess(right, type); if (rightAccess) { return narrowTypeByDiscriminantProperty(type, rightAccess, operator, left, assumeTrue); } if (isMatchingConstructorReference(left)) { return narrowTypeByConstructor(type, operator, right, assumeTrue); } if (isMatchingConstructorReference(right)) { return narrowTypeByConstructor(type, operator, left, assumeTrue); } if (isBooleanLiteral(right) && !isAccessExpression(left)) { return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue); } if (isBooleanLiteral(left) && !isAccessExpression(right)) { return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue); } break; case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr as InstanceofExpression, assumeTrue); case SyntaxKind.InKeyword: if (isPrivateIdentifier(expr.left)) { return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); } const target = getReferenceCandidate(expr.right); if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target)) { const leftType = getTypeOfExpression(expr.left); if (isTypeUsableAsPropertyName(leftType) && getAccessedPropertyName(reference) === getPropertyNameFromType(leftType)) { return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); } } if (isMatchingReference(reference, target)) { const leftType = getTypeOfExpression(expr.left); if (isTypeUsableAsPropertyName(leftType)) { return narrowTypeByInKeyword(type, leftType, assumeTrue); } } break; case SyntaxKind.CommaToken: return narrowType(type, expr.right, assumeTrue); // Ordinarily we won't see && and || expressions in control flow analysis because the Binder breaks those // expressions down to individual conditional control flows. However, we may encounter them when analyzing // aliased conditional expressions. case SyntaxKind.AmpersandAmpersandToken: return assumeTrue ? narrowType(narrowType(type, expr.left, /*assumeTrue*/ true), expr.right, /*assumeTrue*/ true) : getUnionType([narrowType(type, expr.left, /*assumeTrue*/ false), narrowType(type, expr.right, /*assumeTrue*/ false)]); case SyntaxKind.BarBarToken: return assumeTrue ? getUnionType([narrowType(type, expr.left, /*assumeTrue*/ true), narrowType(type, expr.right, /*assumeTrue*/ true)]) : narrowType(narrowType(type, expr.left, /*assumeTrue*/ false), expr.right, /*assumeTrue*/ false); } return type; } function narrowTypeByPrivateIdentifierInInExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { const target = getReferenceCandidate(expr.right); if (!isMatchingReference(reference, target)) { return type; } Debug.assertNode(expr.left, isPrivateIdentifier); const symbol = getSymbolForPrivateIdentifierExpression(expr.left); if (symbol === undefined) { return type; } const classSymbol = symbol.parent!; const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration")) ? getTypeOfSymbol(classSymbol) as InterfaceType : getDeclaredTypeOfSymbol(classSymbol); return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true); } function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { // We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows: // When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch. // When operator is !== and type of value excludes undefined, null and undefined is removed from type of obj in false branch. // When operator is == and type of value excludes null and undefined, null and undefined is removed from type of obj in true branch. // When operator is != and type of value excludes null and undefined, null and undefined is removed from type of obj in false branch. // When operator is === and type of value is undefined, null and undefined is removed from type of obj in false branch. // When operator is !== and type of value is undefined, null and undefined is removed from type of obj in true branch. // When operator is == and type of value is null or undefined, null and undefined is removed from type of obj in false branch. // When operator is != and type of value is null or undefined, null and undefined is removed from type of obj in true branch. const equalsOperator = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; const nullableFlags = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? TypeFlags.Nullable : TypeFlags.Undefined; const valueType = getTypeOfExpression(value); // Note that we include any and unknown in the exclusion test because their domain includes null and undefined. const removeNullable = equalsOperator !== assumeTrue && everyType(valueType, t => !!(t.flags & nullableFlags)) || equalsOperator === assumeTrue && everyType(valueType, t => !(t.flags & (TypeFlags.AnyOrUnknown | nullableFlags))); return removeNullable ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { if (type.flags & TypeFlags.Any) { return type; } if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { assumeTrue = !assumeTrue; } const valueType = getTypeOfExpression(value); const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; if (valueType.flags & TypeFlags.Nullable) { if (!strictNullChecks) { return type; } const facts = doubleEquals ? assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : valueType.flags & TypeFlags.Null ? assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; return getAdjustedTypeWithFacts(type, facts); } if (assumeTrue) { if (!doubleEquals && (type.flags & TypeFlags.Unknown || someType(type, isEmptyAnonymousObjectType))) { if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) || isEmptyAnonymousObjectType(valueType)) { return valueType; } if (valueType.flags & TypeFlags.Object) { return nonPrimitiveType; } } const filteredType = filterType(type, t => areTypesComparable(t, valueType) || doubleEquals && isCoercibleUnderDoubleEquals(t, valueType)); return replacePrimitivesWithLiterals(filteredType, valueType); } if (isUnitType(valueType)) { return filterType(type, t => !(isUnitLikeType(t) && areTypesComparable(t, valueType))); } return type; } function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { assumeTrue = !assumeTrue; } const target = getReferenceCandidate(typeOfExpr.expression); if (!isMatchingReference(reference, target)) { if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } const propertyAccess = getDiscriminantPropertyAccess(target, type); if (propertyAccess) { return narrowTypeByDiscriminant(type, propertyAccess, t => narrowTypeByLiteralExpression(t, literal, assumeTrue)); } return type; } return narrowTypeByLiteralExpression(type, literal, assumeTrue); } function narrowTypeByLiteralExpression(type: Type, literal: LiteralExpression, assumeTrue: boolean) { return assumeTrue ? narrowTypeByTypeName(type, literal.text) : getAdjustedTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject); } function narrowTypeBySwitchOptionalChainContainment(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData, clauseCheck: (type: Type) => boolean) { const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } function narrowTypeBySwitchOnDiscriminant(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData) { // We only narrow if all case expressions specify // values with unit types, except for the case where // `type` is unknown. In this instance we map object // types to the nonPrimitive type and narrow with that. const switchTypes = getSwitchClauseTypes(switchStatement); if (!switchTypes.length) { return type; } const clauseTypes = switchTypes.slice(clauseStart, clauseEnd); const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType); if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) { let groundClauseTypes: Type[] | undefined; for (let i = 0; i areTypesComparable(discriminantType, t)), discriminantType); if (!hasDefaultClause) { return caseType; } const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, t.flags & TypeFlags.Undefined ? undefinedType : getRegularTypeOfLiteralType(extractUnitType(t))))); return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]); } function narrowTypeByTypeName(type: Type, typeName: string) { switch (typeName) { case "string": return narrowTypeByTypeFacts(type, stringType, TypeFacts.TypeofEQString); case "number": return narrowTypeByTypeFacts(type, numberType, TypeFacts.TypeofEQNumber); case "bigint": return narrowTypeByTypeFacts(type, bigintType, TypeFacts.TypeofEQBigInt); case "boolean": return narrowTypeByTypeFacts(type, booleanType, TypeFacts.TypeofEQBoolean); case "symbol": return narrowTypeByTypeFacts(type, esSymbolType, TypeFacts.TypeofEQSymbol); case "object": return type.flags & TypeFlags.Any ? type : getUnionType([narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQObject), narrowTypeByTypeFacts(type, nullType, TypeFacts.EQNull)]); case "function": return type.flags & TypeFlags.Any ? type : narrowTypeByTypeFacts(type, globalFunctionType, TypeFacts.TypeofEQFunction); case "undefined": return narrowTypeByTypeFacts(type, undefinedType, TypeFacts.EQUndefined); } return narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQHostObject); } function narrowTypeByTypeFacts(type: Type, impliedType: Type, facts: TypeFacts) { return mapType(type, t => // We first check if a constituent is a subtype of the implied type. If so, we either keep or eliminate // the constituent based on its type facts. We use the strict subtype relation because it treats `object` // as a subtype of `{}`, and we need the type facts check because function types are subtypes of `object`, // but are classified as "function" according to `typeof`. isTypeRelatedTo(t, impliedType, strictSubtypeRelation) ? hasTypeFacts(t, facts) ? t : neverType : // We next check if the consituent is a supertype of the implied type. If so, we substitute the implied // type. This handles top types like `unknown` and `{}`, and supertypes like `{ toString(): string }`. isTypeSubtypeOf(impliedType, t) ? impliedType : // Neither the constituent nor the implied type is a subtype of the other, however their domains may still // overlap. For example, an unconstrained type parameter and type `string`. If the type facts indicate // possible overlap, we form an intersection. Otherwise, we eliminate the constituent. hasTypeFacts(t, facts) ? getIntersectionType([t, impliedType]) : neverType); } function narrowTypeBySwitchOnTypeOf(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): Type { const witnesses = getSwitchClauseTypeOfWitnesses(switchStatement); if (!witnesses) { return type; } // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause. const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause); const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex getTypeFacts(t, notEqualFacts) === notEqualFacts); } // In the non-default cause we create a union of the type narrowed by each of the listed cases. const clauseWitnesses = witnesses.slice(clauseStart, clauseEnd); return getUnionType(map(clauseWitnesses, text => text ? narrowTypeByTypeName(type, text) : neverType)); } function narrowTypeBySwitchOnTrue(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): Type { const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause); const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex clause.kind === SyntaxKind.CaseClause ? narrowType(type, clause.expression, /*assumeTrue*/ true) : neverType)); } function isMatchingConstructorReference(expr: Expression) { return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" || isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") && isMatchingReference(reference, expr.expression); } function narrowTypeByConstructor(type: Type, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type { // Do not narrow when checking inequality. if (assumeTrue ? (operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) : (operator !== SyntaxKind.ExclamationEqualsToken && operator !== SyntaxKind.ExclamationEqualsEqualsToken)) { return type; } // Get the type of the constructor identifier expression, if it is not a function then do not narrow. const identifierType = getTypeOfExpression(identifier); if (!isFunctionType(identifierType) && !isConstructorType(identifierType)) { return type; } // Get the prototype property of the type identifier so we can find out its type. const prototypeProperty = getPropertyOfType(identifierType, "prototype" as __String); if (!prototypeProperty) { return type; } // Get the type of the prototype, if it is undefined, or the global `Object` or `Function` types then do not narrow. const prototypeType = getTypeOfSymbol(prototypeProperty); const candidate = !isTypeAny(prototypeType) ? prototypeType : undefined; if (!candidate || candidate === globalObjectType || candidate === globalFunctionType) { return type; } // If the type that is being narrowed is `any` then just return the `candidate` type since every type is a subtype of `any`. if (isTypeAny(type)) { return candidate; } // Filter out types that are not considered to be "constructed by" the `candidate` type. return filterType(type, t => isConstructedBy(t, candidate)); function isConstructedBy(source: Type, target: Type) { // If either the source or target type are a class type then we need to check that they are the same exact type. // This is because you may have a class `A` that defines some set of properties, and another class `B` // that defines the same set of properties as class `A`, in that case they are structurally the same // type, but when you do something like `instanceOfA.constructor === B` it will return false. if ( source.flags & TypeFlags.Object && getObjectFlags(source) & ObjectFlags.Class || target.flags & TypeFlags.Object && getObjectFlags(target) & ObjectFlags.Class ) { return source.symbol === target.symbol; } // For all other types just check that the `source` type is a subtype of the `target` type. return isTypeSubtypeOf(source, target); } } function narrowTypeByInstanceof(type: Type, expr: InstanceofExpression, assumeTrue: boolean): Type { const left = getReferenceCandidate(expr.left); if (!isMatchingReference(reference, left)) { if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) { return getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } return type; } const right = expr.right; const rightType = getTypeOfExpression(right); if (!isTypeDerivedFrom(rightType, globalObjectType)) { return type; } // if the right-hand side has an object type with a custom `[Symbol.hasInstance]` method, and that method // has a type predicate, use the type predicate to perform narrowing. This allows normal `object` types to // participate in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator. const signature = getEffectsSignature(expr); const predicate = signature && getTypePredicateOfSignature(signature); if (predicate && predicate.kind === TypePredicateKind.Identifier && predicate.parameterIndex === 0) { return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ true); } if (!isTypeDerivedFrom(rightType, globalFunctionType)) { return type; } const instanceType = mapType(rightType, getInstanceType); // Don't narrow from `any` if the target type is exactly `Object` or `Function`, and narrow // in the false branch only if the target is a non-empty object type. if ( isTypeAny(type) && (instanceType === globalObjectType || instanceType === globalFunctionType) || !assumeTrue && !(instanceType.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(instanceType)) ) { return type; } return getNarrowedType(type, instanceType, assumeTrue, /*checkDerived*/ true); } function getInstanceType(constructorType: Type) { const prototypePropertyType = getTypeOfPropertyOfType(constructorType, "prototype" as __String); if (prototypePropertyType && !isTypeAny(prototypePropertyType)) { return prototypePropertyType; } const constructSignatures = getSignaturesOfType(constructorType, SignatureKind.Construct); if (constructSignatures.length) { return getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))); } // We use the empty object type to indicate we don't know the type of objects created by // this constructor function. return emptyObjectType; } function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean): Type { const key = type.flags & TypeFlags.Union ? `N${getTypeId(type)},${getTypeId(candidate)},${(assumeTrue ? 1 : 0) | (checkDerived ? 2 : 0)}` : undefined; return getCachedType(key) ?? setCachedType(key, getNarrowedTypeWorker(type, candidate, assumeTrue, checkDerived)); } function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { if (!assumeTrue) { if (type === candidate) { return neverType; } if (checkDerived) { return filterType(type, t => !isTypeDerivedFrom(t, candidate)); } type = type.flags & TypeFlags.Unknown ? unknownUnionType : type; const trueType = getNarrowedType(type, candidate, /*assumeTrue*/ true, /*checkDerived*/ false); return recombineUnknownType(filterType(type, t => !isTypeSubsetOf(t, trueType))); } if (type.flags & TypeFlags.AnyOrUnknown) { return candidate; } if (type === candidate) { return candidate; } // We first attempt to filter the current type, narrowing constituents as appropriate and removing // constituents that are unrelated to the candidate. const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf; const keyPropertyName = type.flags & TypeFlags.Union ? getKeyPropertyName(type as UnionType) : undefined; const narrowedType = mapType(candidate, c => { // If a discriminant property is available, use that to reduce the type. const discriminant = keyPropertyName && getTypeOfPropertyOfType(c, keyPropertyName); const matching = discriminant && getConstituentTypeForKeyType(type as UnionType, discriminant); // For each constituent t in the current type, if t and and c are directly related, pick the most // specific of the two. When t and c are related in both directions, we prefer c for type predicates // because that is the asserted type, but t for `instanceof` because generics aren't reflected in // prototype object types. const directlyRelated = mapType( matching || type, checkDerived ? t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType : t => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType, ); // If no constituents are directly related, create intersections for any generic constituents that // are related by constraint. return directlyRelated.flags & TypeFlags.Never ? mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) : directlyRelated; }); // If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two // based on assignability, or as a last resort produce an intersection. return !(narrowedType.flags & TypeFlags.Never) ? narrowedType : isTypeSubtypeOf(candidate, type) ? candidate : isTypeAssignableTo(type, candidate) ? type : isTypeAssignableTo(candidate, type) ? candidate : getIntersectionType([type, candidate]); } function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { if (hasMatchingArgument(callExpression, reference)) { const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; const predicate = signature && getTypePredicateOfSignature(signature); if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) { return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue); } } if (containsMissingType(type) && isAccessExpression(reference) && isPropertyAccessExpression(callExpression.expression)) { const callAccess = callExpression.expression; if ( isMatchingReference(reference.expression, getReferenceCandidate(callAccess.expression)) && isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && callExpression.arguments.length === 1 ) { const argument = callExpression.arguments[0]; if (isStringLiteralLike(argument) && getAccessedPropertyName(reference) === escapeLeadingUnderscores(argument.text)) { return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); } } } return type; } function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type { // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) { const predicateArgument = getTypePredicateArgument(predicate, callExpression); if (predicateArgument) { if (isMatchingReference(reference, predicateArgument)) { return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false); } if ( strictNullChecks && optionalChainContainsReference(predicateArgument, reference) && ( assumeTrue && !(hasTypeFacts(predicate.type, TypeFacts.EQUndefined)) || !assumeTrue && everyType(predicate.type, isNullableType) ) ) { type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } const access = getDiscriminantPropertyAccess(predicateArgument, type); if (access) { return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, /*checkDerived*/ false)); } } } return type; } // Narrow the given type based on the given expression having the assumed boolean value. The returned type // will be a subtype or the same type as the argument. function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` if ( isExpressionOfOptionalChainRoot(expr) || isBinaryExpression(expr.parent) && (expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken || expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionEqualsToken) && expr.parent.left === expr ) { return narrowTypeByOptionality(type, expr, assumeTrue); } switch (expr.kind) { case SyntaxKind.Identifier: // When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline // up to five levels of aliased conditional expressions that are themselves declared as const variables. if (!isMatchingReference(reference, expr) && inlineLevel getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); } return type; } } function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { symbol = getExportSymbolOfValueSymbolIfExported(symbol); // If we have an identifier or a property access at the given location, if the location is // an dotted name expression, and if the location is not an assignment target, obtain the type // of the expression (which will reflect control flow analysis). If the expression indeed // resolved to the given symbol, return the narrowed type. if (location.kind === SyntaxKind.Identifier || location.kind === SyntaxKind.PrivateIdentifier) { if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { location = location.parent; } if (isExpressionNode(location) && (!isAssignmentTarget(location) || isWriteAccess(location))) { const type = removeOptionalTypeMarker( isWriteAccess(location) && location.kind === SyntaxKind.PropertyAccessExpression ? checkPropertyAccessExpression(location as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true) : getTypeOfExpression(location as Expression), ); if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { return type; } } } if (isDeclarationName(location) && isSetAccessor(location.parent) && getAnnotatedAccessorTypeNode(location.parent)) { return getWriteTypeOfAccessors(location.parent.symbol); } // The location isn't a reference to the given symbol, meaning we're being asked // a hypothetical question of what type the symbol would have if there was a reference // to it at the given location. Since we have no control flow information for the // hypothetical reference (control flow information is created and attached by the // binder), we simply return the declared type of the symbol. return isRightSideOfAccessExpression(location) && isWriteAccess(location.parent) ? getWriteTypeOfSymbol(symbol) : getNonMissingTypeOfSymbol(symbol); } function getControlFlowContainer(node: Node): Node { return findAncestor(node.parent, node => isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || node.kind === SyntaxKind.ModuleBlock || node.kind === SyntaxKind.SourceFile || node.kind === SyntaxKind.PropertyDeclaration)!; } // Check if a parameter, catch variable, or mutable local variable is assigned anywhere definitely function isSymbolAssignedDefinitely(symbol: Symbol) { if (symbol.lastAssignmentPos !== undefined) { return symbol.lastAssignmentPos e.kind !== SyntaxKind.OmittedExpression && isSomeSymbolAssignedWorker(e.name)); } function hasParentWithAssignmentsMarked(node: Node) { return !!findAncestor(node.parent, node => isFunctionOrSourceFile(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); } function isFunctionOrSourceFile(node: Node) { return isFunctionLikeDeclaration(node) || isSourceFile(node); } // For all assignments within the given root node, record the last assignment source position for all // referenced parameters and mutable local variables. When assignments occur in nested functions or // references occur in export specifiers, record Number.MAX_VALUE as the assignment position. When // assignments occur in compound statements, record the ending source position of the compound statement // as the assignment position (this is more conservative than full control flow analysis, but requires // only a single walk over the AST). function markNodeAssignments(node: Node) { switch (node.kind) { case SyntaxKind.Identifier: const assigmentTarget = getAssignmentTargetKind(node); if (assigmentTarget !== AssignmentKind.None) { const symbol = getResolvedSymbol(node as Identifier); const hasDefiniteAssignment = assigmentTarget === AssignmentKind.Definite || (symbol.lastAssignmentPos !== undefined && symbol.lastAssignmentPos 0) { symbol.lastAssignmentPos *= -1; } } } return; case SyntaxKind.ExportSpecifier: const exportDeclaration = (node as ExportSpecifier).parent.parent; const name = (node as ExportSpecifier).propertyName || (node as ExportSpecifier).name; if (!(node as ExportSpecifier).isTypeOnly && !exportDeclaration.isTypeOnly && !exportDeclaration.moduleSpecifier && name.kind !== SyntaxKind.StringLiteral) { const symbol = resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true); if (symbol && isParameterOrMutableLocalVariable(symbol)) { const sign = symbol.lastAssignmentPos !== undefined && symbol.lastAssignmentPos declaration.pos) { switch (node.kind) { case SyntaxKind.VariableStatement: case SyntaxKind.ExpressionStatement: case SyntaxKind.IfStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: case SyntaxKind.WithStatement: case SyntaxKind.SwitchStatement: case SyntaxKind.TryStatement: case SyntaxKind.ClassDeclaration: pos = node.end; } node = node.parent; } return pos; } function isConstantVariable(symbol: Symbol) { return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Constant) !== 0; } function isParameterOrMutableLocalVariable(symbol: Symbol) { // Return true if symbol is a parameter, a catch clause variable, or a mutable local variable const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration); return !!declaration && ( isParameter(declaration) || isVariableDeclaration(declaration) && (isCatchClause(declaration.parent) || isMutableLocalVariableDeclaration(declaration)) ); } function isMutableLocalVariableDeclaration(declaration: VariableDeclaration) { // Return true if symbol is a non-exported and non-global `let` variable return !!(declaration.parent.flags & NodeFlags.Let) && !( getCombinedModifierFlags(declaration) & ModifierFlags.Export || declaration.parent.parent.kind === SyntaxKind.VariableStatement && isGlobalSourceFile(declaration.parent.parent.parent) ); } function parameterInitializerContainsUndefined(declaration: ParameterDeclaration): boolean { const links = getNodeLinks(declaration); if (links.parameterInitializerContainsUndefined === undefined) { if (!pushTypeResolution(declaration, TypeSystemPropertyName.ParameterInitializerContainsUndefined)) { reportCircularityError(declaration.symbol); return true; } const containsUndefined = !!(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)); if (!popTypeResolution()) { reportCircularityError(declaration.symbol); return true; } links.parameterInitializerContainsUndefined ??= containsUndefined; } return links.parameterInitializerContainsUndefined; } /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type { const removeUndefined = strictNullChecks && declaration.kind === SyntaxKind.Parameter && declaration.initializer && hasTypeFacts(declaredType, TypeFacts.IsUndefined) && !parameterInitializerContainsUndefined(declaration); return removeUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; } function isConstraintPosition(type: Type, node: Node) { const parent = node.parent; // In an element access obj[x], we consider obj to be in a constraint position, except when obj is of // a generic type without a nullable constraint and x is a generic type. This is because when both obj // and x are of generic types T and K, we want the resulting type to be T[K]. return parent.kind === SyntaxKind.PropertyAccessExpression || parent.kind === SyntaxKind.QualifiedName || parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node || parent.kind === SyntaxKind.NewExpression && (parent as NewExpression).expression === node || parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node && !(someType(type, isGenericTypeWithoutNullableConstraint) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression))); } function isGenericTypeWithUnionConstraint(type: Type): boolean { return type.flags & TypeFlags.Intersection ? some((type as IntersectionType).types, isGenericTypeWithUnionConstraint) : !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union)); } function isGenericTypeWithoutNullableConstraint(type: Type): boolean { return type.flags & TypeFlags.Intersection ? some((type as IntersectionType).types, isGenericTypeWithoutNullableConstraint) : !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable)); } function hasContextualTypeWithNoGenericTypes(node: Node, checkMode: CheckMode | undefined) { // Computing the contextual type for a child of a JSX element involves resolving the type of the // element's tag name, so we exclude that here to avoid circularities. // If check mode has `CheckMode.RestBindingElement`, we skip binding pattern contextual types, // as we want the type of a rest element to be generic when possible. const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) && !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) && (checkMode && checkMode & CheckMode.RestBindingElement ? getContextualType(node, ContextFlags.SkipBindingPatterns) : getContextualType(node, /*contextFlags*/ undefined)); return contextualType && !isGenericType(contextualType); } function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) { if (isNoInferType(type)) { type = (type as SubstitutionType).baseType; } // When the type of a reference is or contains an instantiable type with a union type constraint, and // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or // has a contextual type containing no top-level instantiables (meaning constraints will determine // assignability), we substitute constraints for all instantiables in the type of the reference to give // control flow analysis an opportunity to narrow it further. For example, for a reference of a type // parameter type 'T extends string | undefined' with a contextual type 'string', we substitute // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && someType(type, isGenericTypeWithUnionConstraint) && (isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type; } function isExportOrExportExpression(location: Node) { return !!findAncestor(location, n => { const parent = n.parent; if (parent === undefined) { return "quit"; } if (isExportAssignment(parent)) { return parent.expression === n && isEntityNameExpression(n); } if (isExportSpecifier(parent)) { return parent.name === n || parent.propertyName === n; } return false; }); } /** * This function marks all the imports the given location refers to as `.referenced` in `NodeLinks` (transitively through local import aliases). * (This corresponds to not getting elided in JS emit.) * It can be called on *most* nodes in the AST with `ReferenceHint.Unspecified` and will filter its inputs, but care should be taken to avoid calling it on the RHS of an `import =` or specifiers in a `import {} from "..."`, * unless you *really* want to *definitely* mark those as referenced. * These shouldn't be directly marked, and should only get marked transitively by the internals of this function. * * @param location The location to mark js import refernces for * @param hint The kind of reference `location` has already been checked to be * @param propSymbol The optional symbol of the property we're looking up - this is used for property accesses when `const enum`s do not count as references (no `isolatedModules`, no `preserveConstEnums` + export). It will be calculated if not provided. * @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided (but is costly). */ function markLinkedReferences(location: PropertyAccessExpression | QualifiedName, hint: ReferenceHint.Property, propSymbol: Symbol | undefined, parentType: Type): void; function markLinkedReferences(location: Identifier, hint: ReferenceHint.Identifier): void; function markLinkedReferences(location: ExportAssignment, hint: ReferenceHint.ExportAssignment): void; function markLinkedReferences(location: JsxOpeningLikeElement | JsxOpeningFragment, hint: ReferenceHint.Jsx): void; function markLinkedReferences(location: FunctionLikeDeclaration | MethodSignature, hint: ReferenceHint.AsyncFunction): void; function markLinkedReferences(location: ImportEqualsDeclaration, hint: ReferenceHint.ExportImportEquals): void; function markLinkedReferences(location: ExportSpecifier, hint: ReferenceHint.ExportSpecifier): void; function markLinkedReferences(location: HasDecorators, hint: ReferenceHint.Decorator): void; function markLinkedReferences(location: Node, hint: ReferenceHint.Unspecified, propSymbol?: Symbol, parentType?: Type): void; function markLinkedReferences(location: Node, hint: ReferenceHint, propSymbol?: Symbol, parentType?: Type) { if (!canCollectSymbolAliasAccessabilityData) { return; } if (location.flags & NodeFlags.Ambient && !isPropertySignature(location) && !isPropertyDeclaration(location)) { // References within types and declaration files are never going to contribute to retaining a JS import, // except for properties (which can be decorated). return; } switch (hint) { case ReferenceHint.Identifier: return markIdentifierAliasReferenced(location as Identifier); case ReferenceHint.Property: return markPropertyAliasReferenced(location as PropertyAccessExpression | QualifiedName, propSymbol, parentType); case ReferenceHint.ExportAssignment: return markExportAssignmentAliasReferenced(location as ExportAssignment); case ReferenceHint.Jsx: return markJsxAliasReferenced(location as JsxOpeningLikeElement | JsxOpeningFragment); case ReferenceHint.AsyncFunction: return markAsyncFunctionAliasReferenced(location as FunctionLikeDeclaration | MethodSignature); case ReferenceHint.ExportImportEquals: return markImportEqualsAliasReferenced(location as ImportEqualsDeclaration); case ReferenceHint.ExportSpecifier: return markExportSpecifierAliasReferenced(location as ExportSpecifier); case ReferenceHint.Decorator: return markDecoratorAliasReferenced(location as HasDecorators); case ReferenceHint.Unspecified: { // Identifiers in expression contexts are emitted, so we need to follow their referenced aliases and mark them as used // Some non-expression identifiers are also treated as expression identifiers for this purpose, eg, `a` in `b = {a}` or `q` in `import r = q` // This is the exception, rather than the rule - most non-expression identifiers are declaration names. if (isIdentifier(location) && (isExpressionNode(location) || isShorthandPropertyAssignment(location.parent) || (isImportEqualsDeclaration(location.parent) && location.parent.moduleReference === location)) && shouldMarkIdentifierAliasReferenced(location)) { if (isPropertyAccessOrQualifiedName(location.parent)) { const left = isPropertyAccessExpression(location.parent) ? location.parent.expression : location.parent.left; if (left !== location) return; // Only mark the LHS (the RHS is a property lookup) } markIdentifierAliasReferenced(location); return; } if (isPropertyAccessOrQualifiedName(location)) { let topProp: Node | undefined = location; while (isPropertyAccessOrQualifiedName(topProp)) { if (isPartOfTypeNode(topProp)) return; topProp = topProp.parent; } return markPropertyAliasReferenced(location); } if (isExportAssignment(location)) { return markExportAssignmentAliasReferenced(location); } if (isJsxOpeningLikeElement(location) || isJsxOpeningFragment(location)) { return markJsxAliasReferenced(location); } if (isImportEqualsDeclaration(location)) { if (isInternalModuleImportEqualsDeclaration(location) || checkExternalImportOrExportDeclaration(location)) { return markImportEqualsAliasReferenced(location); } return; } if (isExportSpecifier(location)) { return markExportSpecifierAliasReferenced(location); } if (isFunctionLikeDeclaration(location) || isMethodSignature(location)) { markAsyncFunctionAliasReferenced(location); // Might be decorated, fall through to decorator final case } if (!compilerOptions.emitDecoratorMetadata) { return; } if (!canHaveDecorators(location) || !hasDecorators(location) || !location.modifiers || !nodeCanBeDecorated(legacyDecorators, location, location.parent, location.parent.parent)) { return; } return markDecoratorAliasReferenced(location); } default: Debug.assertNever(hint, `Unhandled reference hint: ${hint}`); } } function markIdentifierAliasReferenced(location: Identifier) { const symbol = getResolvedSymbol(location); if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) { markAliasReferenced(symbol, location); } } function markPropertyAliasReferenced(location: PropertyAccessExpression | QualifiedName, propSymbol?: Symbol, parentType?: Type) { const left = isPropertyAccessExpression(location) ? location.expression : location.left; if (isThisIdentifier(left) || !isIdentifier(left)) { return; } const parentSymbol = getResolvedSymbol(left); if (!parentSymbol || parentSymbol === unknownSymbol) { return; } // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined // here even if `Foo` is not a const enum. // // The exceptions are: // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. // // The property lookup is deferred as much as possible, in as many situations as possible, to avoid alias marking // pulling on types/symbols it doesn't strictly need to. if (getIsolatedModules(compilerOptions) || (shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location))) { markAliasReferenced(parentSymbol, location); return; } // Hereafter, this relies on type checking - but every check prior to this only used symbol information const leftType = parentType || checkExpressionCached(left); if (isTypeAny(leftType) || leftType === silentNeverType) { markAliasReferenced(parentSymbol, location); return; } let prop = propSymbol; if (!prop && !parentType) { const right = isPropertyAccessExpression(location) ? location.name : location.right; const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); const assignmentKind = getAssignmentTargetKind(location); const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText); } if ( !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && location.parent.kind === SyntaxKind.EnumMember)) ) { markAliasReferenced(parentSymbol, location); } return; } function markExportAssignmentAliasReferenced(location: ExportAssignment) { if (isIdentifier(location.expression)) { const id = location.expression; const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location)); if (sym) { markAliasReferenced(sym, id); } } } function markJsxAliasReferenced(node: JsxOpeningLikeElement | JsxOpeningFragment) { if (!getJsxNamespaceContainerForImplicitImport(node)) { // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.This_JSX_tag_requires_0_to_be_in_scope_but_it_could_not_be_found : undefined; const jsxFactoryNamespace = getJsxNamespace(node); const jsxFactoryLocation = isJsxOpeningLikeElement(node) ? node.tagName : node; const shouldFactoryRefErr = compilerOptions.jsx !== JsxEmit.Preserve && compilerOptions.jsx !== JsxEmit.ReactNative; // #38720/60122, allow null as jsxFragmentFactory let jsxFactorySym: Symbol | undefined; if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { jsxFactorySym = resolveName( jsxFactoryLocation, jsxFactoryNamespace, shouldFactoryRefErr ? SymbolFlags.Value : SymbolFlags.Value & ~SymbolFlags.Enum, jsxFactoryRefErr, /*isUse*/ true, ); } if (jsxFactorySym) { // Mark local symbol as referenced here because it might not have been marked // if jsx emit was not jsxFactory as there wont be error being emitted jsxFactorySym.isReferenced = SymbolFlags.All; // If react/jsxFactory symbol is alias, mark it as refereced if (canCollectSymbolAliasAccessabilityData && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { markAliasSymbolAsReferenced(jsxFactorySym); } } // if JsxFragment, additionally mark jsx pragma as referenced, since `getJsxNamespace` above would have resolved to only the fragment factory if they are distinct if (isJsxOpeningFragment(node)) { const file = getSourceFileOfNode(node); const entity = getJsxFactoryEntity(file); if (entity) { const localJsxNamespace = getFirstIdentifier(entity).escapedText; resolveName( jsxFactoryLocation, localJsxNamespace, shouldFactoryRefErr ? SymbolFlags.Value : SymbolFlags.Value & ~SymbolFlags.Enum, jsxFactoryRefErr, /*isUse*/ true, ); } } } return; } function markAsyncFunctionAliasReferenced(location: FunctionLikeDeclaration | MethodSignature) { if (languageVersion (getSymbolOfDeclaration(node), otherKind); markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); break; case SyntaxKind.MethodDeclaration: for (const parameter of node.parameters) { markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); break; case SyntaxKind.PropertyDeclaration: markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); break; case SyntaxKind.Parameter: markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); const containingSignature = node.parent; for (const parameter of containingSignature.parameters) { markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(containingSignature)); break; } } } function markAliasReferenced(symbol: Symbol, location: Node) { if (!canCollectSymbolAliasAccessabilityData) { return; } if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) { const target = resolveAlias(symbol); if (getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & (SymbolFlags.Value | SymbolFlags.ExportValue)) { // An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled // (because the const enum value will not be inlined), or if (2) the alias is an export // of a const enum declaration that will be preserved. if ( getIsolatedModules(compilerOptions) || shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) ) { markAliasSymbolAsReferenced(symbol); } } } } // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of // the alias as an expression (which recursively takes us back here if the target references another alias). function markAliasSymbolAsReferenced(symbol: Symbol) { Debug.assert(canCollectSymbolAliasAccessabilityData); const links = getSymbolLinks(symbol); if (!links.referenced) { links.referenced = true; const node = getDeclarationOfAliasSymbol(symbol); if (!node) return Debug.fail(); // We defer checking of the reference of an `import =` until the import itself is referenced, // This way a chain of imports can be elided if ultimately the final input is only used in a type // position. if (isInternalModuleImportEqualsDeclaration(node)) { if (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { // import foo = const left = getFirstIdentifier(node.moduleReference as EntityNameExpression); markIdentifierAliasReferenced(left); } } } } function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { const symbol = getSymbolOfDeclaration(node); const target = resolveAlias(symbol); if (target) { const markAlias = target === unknownSymbol || ((getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); if (markAlias) { markAliasSymbolAsReferenced(symbol); } } } function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) { if (!typeName) return; const rootName = getFirstIdentifier(typeName); const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { if ( canCollectSymbolAliasAccessabilityData && symbolIsValue(rootSymbol) && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) && !getTypeOnlyAliasDeclaration(rootSymbol) ) { markAliasSymbolAsReferenced(rootSymbol); } else if ( forDecoratorMetadata && getIsolatedModules(compilerOptions) && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015 && !symbolIsValue(rootSymbol) && !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration) ) { const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled); const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration); if (aliasDeclaration) { addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName))); } } } } /** * If a TypeNode can be resolved to a value symbol imported from an external module, it is * marked as referenced to prevent import elision. */ function markTypeNodeAsReferenced(node: TypeNode | undefined) { markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); } /** * This function marks the type used for metadata decorator as referenced if it is import * from external module. * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in * union and intersection type * @param node */ function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { const entityName = getEntityNameForDecoratorMetadata(node); if (entityName && isEntityName(entityName)) { markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true); } } function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) { const type = getTypeOfSymbol(symbol); const declaration = symbol.valueDeclaration; if (declaration) { // If we have a non-rest binding element with no initializer declared as a const variable or a const-like // parameter (a parameter for which there are no assignments in the function body), and if the parent type // for the destructuring is a union type, one or more of the binding elements may represent discriminant // properties, and we want the effects of conditional checks on such discriminants to affect the types of // other binding elements from the same destructuring. Consider: // // type Action = // | { kind: 'A', payload: number } // | { kind: 'B', payload: string }; // // function f({ kind, payload }: Action) { // if (kind === 'A') { // payload.toFixed(); // } // if (kind === 'B') { // payload.toUpperCase(); // } // } // // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference // as if it occurred in the specified location. We then recompute the narrowed binding element type by // destructuring from the narrowed parent type. if (isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) { const parent = declaration.parent.parent; const rootDeclaration = getRootDeclaration(parent); if (rootDeclaration.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(rootDeclaration) & NodeFlags.Constant || rootDeclaration.kind === SyntaxKind.Parameter) { const links = getNodeLinks(parent); if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) { links.flags |= NodeCheckFlags.InCheckIdentifier; const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal); const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType); links.flags &= ~NodeCheckFlags.InCheckIdentifier; if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) { const pattern = declaration.parent; const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode); if (narrowedType.flags & TypeFlags.Never) { return neverType; } // Destructurings are validated against the parent type elsewhere. Here we disable tuple bounds // checks because the narrowed type may have lower arity than the full parent type. For example, // for the declaration [x, y]: [1, 2] | [3], we may have narrowed the parent type to just [3]. return getBindingElementTypeFromParentType(declaration, narrowedType, /*noTupleBoundsCheck*/ true); } } } } // If we have a const-like parameter with no type annotation or initializer, and if the parameter is contextually // typed by a signature with a single rest parameter of a union of tuple types, one or more of the parameters may // represent discriminant tuple elements, and we want the effects of conditional checks on such discriminants to // affect the types of other parameters in the same parameter list. Consider: // // type Action = [kind: 'A', payload: number] | [kind: 'B', payload: string]; // // const f: (...args: Action) => void = (kind, payload) => { // if (kind === 'A') { // payload.toFixed(); // } // if (kind === 'B') { // payload.toUpperCase(); // } // } // // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use // the arrow function AST node for '(kind, payload) => ...' as a pseudo-reference and narrow this reference as // if it occurred in the specified location. We then recompute the narrowed parameter type by indexing into the // narrowed tuple type. if (isParameter(declaration) && !declaration.type && !declaration.initializer && !declaration.dotDotDotToken) { const func = declaration.parent; if (func.parameters.length >= 2 && isContextSensitiveFunctionOrObjectLiteralMethod(func)) { const contextualSignature = getContextualSignature(func); if (contextualSignature && contextualSignature.parameters.length === 1 && signatureHasRestParameter(contextualSignature)) { const restType = getReducedApparentType(instantiateType(getTypeOfSymbol(contextualSignature.parameters[0]), getInferenceContext(func)?.nonFixingMapper)); if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !some(func.parameters, isSomeSymbolAssigned)) { const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, location.flowNode); const index = func.parameters.indexOf(declaration) - (getThisParameter(func) ? 1 : 0); return getIndexedAccessType(narrowedType, getNumberLiteralType(index)); } } } } } return type; } /** * This part of `checkIdentifier` is kept seperate from the rest, so `NodeCheckFlags` (and related diagnostics) can be lazily calculated * without calculating the flow type of the identifier. */ function checkIdentifierCalculateNodeCheckFlags(node: Identifier, symbol: Symbol) { if (isThisInTypeQuery(node)) return; // As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects. // Although in down-level emit of arrow function, we emit it using function expression which means that // arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects // will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior. // To avoid that we will give an error to users if they use arguments objects in arrow function so that they // can explicitly bound arguments objects if (symbol === argumentsSymbol) { if (isInPropertyInitializerOrClassStaticBlock(node, /*ignoreArrowFunctions*/ true)) { error(node, Diagnostics.arguments_cannot_be_referenced_in_property_initializers_or_class_static_initialization_blocks); return; } let container = getContainingFunction(node); if (container) { if (languageVersion parent === declaration!.parent)) { return nonInferrableAnyType; } let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node); const assignmentKind = getAssignmentTargetKind(node); if (assignmentKind) { if ( !(localOrExportSymbol.flags & SymbolFlags.Variable) && !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule) ) { const assignmentError = localOrExportSymbol.flags & SymbolFlags.Enum ? Diagnostics.Cannot_assign_to_0_because_it_is_an_enum : localOrExportSymbol.flags & SymbolFlags.Class ? Diagnostics.Cannot_assign_to_0_because_it_is_a_class : localOrExportSymbol.flags & SymbolFlags.Module ? Diagnostics.Cannot_assign_to_0_because_it_is_a_namespace : localOrExportSymbol.flags & SymbolFlags.Function ? Diagnostics.Cannot_assign_to_0_because_it_is_a_function : localOrExportSymbol.flags & SymbolFlags.Alias ? Diagnostics.Cannot_assign_to_0_because_it_is_an_import : Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable; error(node, assignmentError, symbolToString(symbol)); return errorType; } if (isReadonlySymbol(localOrExportSymbol)) { if (localOrExportSymbol.flags & SymbolFlags.Variable) { error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol)); } else { error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol)); } return errorType; } } const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; // We only narrow variables and parameters occurring in a non-assignment position. For all other // entities we simply return the declared type. if (localOrExportSymbol.flags & SymbolFlags.Variable) { if (assignmentKind === AssignmentKind.Definite) { return isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type; } } else if (isAlias) { declaration = getDeclarationOfAliasSymbol(symbol); } else { return type; } if (!declaration) { return type; } type = getNarrowableTypeForReference(type, node, checkMode); // The declaration container is the innermost function that encloses the declaration of the variable // or parameter. The flow container is the innermost function starting with which we analyze the control // flow graph to determine the control flow based type. const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; const declarationContainer = getControlFlowContainer(declaration); let flowContainer = getControlFlowContainer(node); const isOuterVariable = flowContainer !== declarationContainer; const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; const typeIsAutomatic = type === autoType || type === autoArrayType; const isAutomaticTypeInNonNull = typeIsAutomatic && node.parent.kind === SyntaxKind.NonNullExpression; // When the control flow originates in a function expression, arrow function, method, or accessor, and // we are referencing a closed-over const variable or parameter or mutable local variable past its last // assignment, we extend the origin of the control flow analysis to include the immediately enclosing // control flow container. while ( flowContainer !== declarationContainer && ( flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer) ) && ( isConstantVariable(localOrExportSymbol) && type !== autoArrayType || isParameterOrMutableLocalVariable(localOrExportSymbol) && isPastLastAssignment(localOrExportSymbol, node) ) ) { flowContainer = getControlFlowContainer(flowContainer); } // We only look for uninitialized variables in strict null checking mode, and only when we can analyze // the entire control flow graph from the variable's declaration (i.e. when the flow container and // declaration container are the same). const isNeverInitialized = immediateDeclaration && isVariableDeclaration(immediateDeclaration) && !immediateDeclaration.initializer && !immediateDeclaration.exclamationToken && isMutableLocalVariableDeclaration(immediateDeclaration) && !isSymbolAssignedDefinitely(symbol); const assumeInitialized = isParameter || isAlias || (isOuterVariable && !isNeverInitialized) || isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) || type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 || isInTypeQuery(node) || isInAmbientOrTypeNode(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || node.parent.kind === SyntaxKind.NonNullExpression || declaration.kind === SyntaxKind.VariableDeclaration && (declaration as VariableDeclaration).exclamationToken || declaration.flags & NodeFlags.Ambient; const initialType = isAutomaticTypeInNonNull ? undefinedType : assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : typeIsAutomatic ? undefinedType : getOptionalType(type); const flowType = isAutomaticTypeInNonNull ? getNonNullableType(getFlowTypeOfReference(node, type, initialType, flowContainer)) : getFlowTypeOfReference(node, type, initialType, flowContainer); // A variable is considered uninitialized when it is possible to analyze the entire control flow graph // from declaration to use, and when the variable's declared type doesn't include undefined but the // control flow based type does include undefined. if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) { if (flowType === autoType || flowType === autoArrayType) { if (noImplicitAny) { error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType)); error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } return convertAutoToAny(flowType); } } else if (!assumeInitialized && !containsUndefinedType(type) && containsUndefinedType(flowType)) { error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); // Return the declared type to reduce follow-on errors return type; } return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } function isSameScopedBindingElement(node: Identifier, declaration: Declaration) { if (isBindingElement(declaration)) { const bindingElement = findAncestor(node, isBindingElement); return bindingElement && getRootDeclaration(bindingElement) === getRootDeclaration(declaration); } } function shouldMarkIdentifierAliasReferenced(node: Identifier): boolean { const parent = node.parent; if (parent) { // A property access expression LHS? checkPropertyAccessExpression will handle that. if (isPropertyAccessExpression(parent) && parent.expression === node) { return false; } // Next two check for an identifier inside a type only export. if (isExportSpecifier(parent) && parent.isTypeOnly) { return false; } const greatGrandparent = parent.parent?.parent; if (greatGrandparent && isExportDeclaration(greatGrandparent) && greatGrandparent.isTypeOnly) { return false; } } return true; } function isInsideFunctionOrInstancePropertyInitializer(node: Node, threshold: Node): boolean { return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n) || ( n.parent && isPropertyDeclaration(n.parent) && !hasStaticModifier(n.parent) && n.parent.initializer === n )); } function getPartOfForStatementContainingNode(node: Node, container: ForStatement) { return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement); } function getEnclosingIterationStatement(node: Node): Node | undefined { return findAncestor(node, n => (!n || nodeStartsNewLexicalEnvironment(n)) ? "quit" : isIterationStatement(n, /*lookInLabeledStatements*/ false)); } function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { if ( languageVersion >= ScriptTarget.ES2015 || (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || !symbol.valueDeclaration || isSourceFile(symbol.valueDeclaration) || symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause ) { return; } // 1. walk from the use site up to the declaration and check // if there is anything function like between declaration and use-site (is binding/class is captured in function). // 2. walk from the declaration up to the boundary of lexical environment and check // if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement) const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); const isCaptured = isInsideFunctionOrInstancePropertyInitializer(node, container); const enclosingIterationStatement = getEnclosingIterationStatement(container); if (enclosingIterationStatement) { if (isCaptured) { // mark iteration statement as containing block-scoped binding captured in some function let capturesBlockScopeBindingInLoopBody = true; if (isForStatement(container)) { const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList); if (varDeclList && varDeclList.parent === container) { const part = getPartOfForStatementContainingNode(node.parent, container); if (part) { const links = getNodeLinks(part); links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding; const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []); pushIfUnique(capturedBindings, symbol); if (part === container.initializer) { capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body } } } } if (capturesBlockScopeBindingInLoopBody) { getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; } } // mark variables that are declared in loop initializer and reassigned inside the body of ForStatement. // if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back. if (isForStatement(container)) { const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList); if (varDeclList && varDeclList.parent === container && isAssignedInBodyOfForStatement(node, container)) { getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter; } } // set 'declared inside loop' bit on the block-scoped binding getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; } if (isCaptured) { getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; } } function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { const links = getNodeLinks(node); return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfDeclaration(decl)); } function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { // skip parenthesized nodes let current: Node = node; while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { current = current.parent; } // check if node is used as LHS in some assignment expression let isAssigned = false; if (isAssignmentTarget(current)) { isAssigned = true; } else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) { const expr = current.parent as PrefixUnaryExpression | PostfixUnaryExpression; isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken; } if (!isAssigned) { return false; } // at this point we know that node is the target of assignment // now check that modification happens inside the statement part of the ForStatement return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); } function captureLexicalThis(node: Node, container: Node): void { getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) { const classNode = container.parent; getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis; } else { getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis; } } function findFirstSuperCall(node: Node): SuperCall | undefined { return isSuperCall(node) ? node : isFunctionLike(node) ? undefined : forEachChild(node, findFirstSuperCall); } /** * Check if the given class-declaration extends null then return true. * Otherwise, return false * @param classDecl a class declaration to check if it extends null */ function classDeclarationExtendsNull(classDecl: ClassLikeDeclaration): boolean { const classSymbol = getSymbolOfDeclaration(classDecl); const classInstanceType = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); return baseConstructorType === nullWideningType; } function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) { const containingClassDecl = container.parent as ClassDeclaration; const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl); // If a containing class does not have extends clause or the class extends null // skip checking whether super statement is called before "this" accessing. if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { if (canHaveFlowNode(node) && node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { error(node, diagnosticMessage); } } } function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) { if ( isPropertyDeclaration(container) && hasStaticModifier(container) && legacyDecorators && container.initializer && textRangeContainsPositionInclusive(container.initializer, thisExpression.pos) && hasDecorators(container.parent) ) { error(thisExpression, Diagnostics.Cannot_use_this_in_a_static_property_initializer_of_a_decorated_class); } } function checkThisExpression(node: Node): Type { const isNodeInTypeQuery = isInTypeQuery(node); // Stop at the first arrow function so that we can // tell whether 'this' needs to be captured. let container = getThisContainer(node, /*includeArrowFunctions*/ true, /*includeClassComputedPropertyName*/ true); let capturedByArrowFunction = false; let thisInComputedPropertyName = false; if (container.kind === SyntaxKind.Constructor) { checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); } while (true) { // Now skip arrow functions to get the "real" owner of 'this'. if (container.kind === SyntaxKind.ArrowFunction) { container = getThisContainer(container, /*includeArrowFunctions*/ false, !thisInComputedPropertyName); capturedByArrowFunction = true; } if (container.kind === SyntaxKind.ComputedPropertyName) { container = getThisContainer(container, !capturedByArrowFunction, /*includeClassComputedPropertyName*/ false); thisInComputedPropertyName = true; continue; } break; } checkThisInStaticClassFieldInitializerInDecoratedClass(node, container); if (thisInComputedPropertyName) { error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); } else { switch (container.kind) { case SyntaxKind.ModuleDeclaration: error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body); // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks break; case SyntaxKind.EnumDeclaration: error(node, Diagnostics.this_cannot_be_referenced_in_current_location); // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks break; } } // When targeting es6, mark that we'll need to capture `this` in its lexically bound scope. if (!isNodeInTypeQuery && capturedByArrowFunction && languageVersion void) => void }); // Object.defineProperty(x, "method", { get: () => function() { }) }); else if ( container.kind === SyntaxKind.FunctionExpression && isPropertyAssignment(container.parent) && isIdentifier(container.parent.name) && (container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") && isObjectLiteralExpression(container.parent.parent) && isCallExpression(container.parent.parent.parent) && container.parent.parent.parent.arguments[2] === container.parent.parent && getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty ) { return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression; } // Object.defineProperty(x, "method", { value() { } }); // Object.defineProperty(x, "method", { set(x: () => void) {} }); // Object.defineProperty(x, "method", { get() { return () => {} } }); else if ( isMethodDeclaration(container) && isIdentifier(container.name) && (container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") && isObjectLiteralExpression(container.parent) && isCallExpression(container.parent.parent) && container.parent.parent.arguments[2] === container.parent && getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty ) { return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression; } } function getTypeForThisExpressionFromJSDoc(node: SignatureDeclaration) { const thisTag = getJSDocThisTag(node); if (thisTag && thisTag.typeExpression) { return getTypeFromTypeNode(thisTag.typeExpression); } const signature = getSignatureOfTypeTag(node); if (signature) { return getThisTypeOfSignature(signature); } } function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); } function checkSuperExpression(node: Node): Type { const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node; const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); let container = immediateContainer; let needToCaptureLexicalThis = false; let inAsyncFunction = false; // adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting if (!isCallExpression) { while (container && container.kind === SyntaxKind.ArrowFunction) { if (hasSyntacticModifier(container, ModifierFlags.Async)) inAsyncFunction = true; container = getSuperContainer(container, /*stopOnFunctions*/ true); needToCaptureLexicalThis = languageVersion 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 { if (!isSourceFile(current) || isExternalOrCommonJsModule(current)) { getNodeLinks(current).flags |= NodeCheckFlags.ContainsSuperPropertyInStaticInitializer; } }); } } else { nodeCheckFlag = NodeCheckFlags.SuperInstance; } getNodeLinks(node).flags |= nodeCheckFlag; // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. // This is due to the fact that we emit the body of an async function inside of a generator function. As generator // functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper // uses an arrow function, which is permitted to reference `super`. // // There are two primary ways we can access `super` from within an async method. The first is getting the value of a property // or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value // of a property or indexed access, either as part of an assignment expression or destructuring assignment. // // The simplest case is reading a value, in which case we will emit something like the following: // // // ts // ... // async asyncMethod() { // let x = await super.asyncMethod(); // return x; // } // ... // // // js // ... // asyncMethod() { // const _super = Object.create(null, { // asyncMethod: { get: () => super.asyncMethod }, // }); // return __awaiter(this, arguments, Promise, function *() { // let x = yield _super.asyncMethod.call(this); // return x; // }); // } // ... // // The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases // are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment: // // // ts // ... // async asyncMethod(ar: Promise) { // [super.a, super.b] = await ar; // } // ... // // // js // ... // asyncMethod(ar) { // const _super = Object.create(null, { // a: { get: () => super.a, set: (v) => super.a = v }, // b: { get: () => super.b, set: (v) => super.b = v } // }; // return __awaiter(this, arguments, Promise, function *() { // [_super.a, _super.b] = yield ar; // }); // } // ... // // Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments // as a call expression cannot be used as the target of a destructuring assignment while a property access can. // // For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations. if (container.kind === SyntaxKind.MethodDeclaration && inAsyncFunction) { if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync; } else { getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAccessInAsync; } } if (needToCaptureLexicalThis) { // call expressions are allowed only in constructors so they should always capture correct 'this' // super property access expressions can also appear in arrow functions - // in this case they should also use correct lexical this captureLexicalThis(node.parent, container); } if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) { if (languageVersion { return t.flags & TypeFlags.Intersection ? forEach((t as IntersectionType).types, getThisTypeArgument) : getThisTypeArgument(t); }); } function getThisTypeOfObjectLiteralFromContextualType(containingLiteral: ObjectLiteralExpression, contextualType: Type | undefined) { let literal = containingLiteral; let type = contextualType; while (type) { const thisType = getThisTypeFromContextualType(type); if (thisType) { return thisType; } if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { break; } literal = literal.parent.parent as ObjectLiteralExpression; type = getApparentTypeOfContextualType(literal, /*contextFlags*/ undefined); } } function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { if (func.kind === SyntaxKind.ArrowFunction) { return undefined; } if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { const contextualSignature = getContextualSignature(func); if (contextualSignature) { const thisParameter = contextualSignature.thisParameter; if (thisParameter) { return getTypeOfSymbol(thisParameter); } } } const inJs = isInJSFile(func); if (noImplicitThis || inJs) { const containingLiteral = getContainingObjectLiteral(func); if (containingLiteral) { // We have an object literal method. Check if the containing object literal has a contextual type // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in // any directly enclosing object literals. const contextualType = getApparentTypeOfContextualType(containingLiteral, /*contextFlags*/ undefined); const thisType = getThisTypeOfObjectLiteralFromContextualType(containingLiteral, contextualType); if (thisType) { return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); } // There was no contextual ThisType for the containing object literal, so the contextual type // for 'this' is the non-null form of the contextual type for the containing object literal or // the type of the object literal itself. return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral)); } // In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the // contextual type for 'this' is 'obj'. const parent = walkUpParenthesizedExpressions(func.parent); if (isAssignmentExpression(parent)) { const target = parent.left; if (isAccessExpression(target)) { const { expression } = target; // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` if (inJs && isIdentifier(expression)) { const sourceFile = getSourceFileOfNode(parent); if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) { return undefined; } } return getWidenedType(checkExpressionCached(expression)); } } } return undefined; } // Return contextual type of parameter or undefined if no contextual type is available function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined { const func = parameter.parent; if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { return undefined; } const iife = getImmediatelyInvokedFunctionExpression(func); if (iife && iife.arguments) { const args = getEffectiveCallArguments(iife); const indexOfParameter = func.parameters.indexOf(parameter); if (parameter.dotDotDotToken) { return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined, CheckMode.Normal); } const links = getNodeLinks(iife); const cached = links.resolvedSignature; links.resolvedSignature = anySignature; const type = indexOfParameter 0) { return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); } } return undefined; } function getContextualTypeForReturnExpression(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { const func = getContainingFunction(node); if (func) { let contextualReturnType = getContextualReturnType(func, contextFlags); if (contextualReturnType) { const functionFlags = getFunctionFlags(func); if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0; if (contextualReturnType.flags & TypeFlags.Union) { contextualReturnType = filterType(contextualReturnType, type => !!getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator)); } const iterationReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0); if (!iterationReturnType) { return undefined; } contextualReturnType = iterationReturnType; // falls through to unwrap Promise for AsyncGenerators } if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function // Get the awaited type without the `Awaited` 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 | undefined): 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, contextFlags: ContextFlags | undefined): Type | undefined { const func = getContainingFunction(node); if (func) { const functionFlags = getFunctionFlags(func); let contextualReturnType = getContextualReturnType(func, contextFlags); if (contextualReturnType) { const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0; if (!node.asteriskToken && contextualReturnType.flags & TypeFlags.Union) { contextualReturnType = filterType(contextualReturnType, type => !!getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator)); } if (node.asteriskToken) { const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(contextualReturnType, isAsyncGenerator); const yieldType = iterationTypes?.yieldType ?? silentNeverType; const returnType = getContextualType(node, contextFlags) ?? silentNeverType; const nextType = iterationTypes?.nextType ?? unknownType; const generatorType = createGeneratorType(yieldType, returnType, nextType, /*isAsyncGenerator*/ false); if (isAsyncGenerator) { const asyncGeneratorType = createGeneratorType(yieldType, returnType, nextType, /*isAsyncGenerator*/ true); return getUnionType([generatorType, asyncGeneratorType]); } return generatorType; } return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, isAsyncGenerator); } } 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, /*contextFlags*/ undefined); if (contextualReturnType) { return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync) || undefined; } return undefined; } function getContextualReturnType(functionDecl: SignatureDeclaration, contextFlags: ContextFlags | undefined): 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)) { const returnType = getReturnTypeOfSignature(signature); const functionFlags = getFunctionFlags(functionDecl); if (functionFlags & FunctionFlags.Generator) { return filterType(returnType, t => { return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined); }); } if (functionFlags & FunctionFlags.Async) { return filterType(returnType, t => { return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || !!getAwaitedTypeOfPromise(t); }); } return returnType; } const iife = getImmediatelyInvokedFunctionExpression(functionDecl); if (iife) { return getContextualType(iife, contextFlags); } 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 getContextualTypeForDecorator(decorator: Decorator): Type | undefined { const signature = getDecoratorCallSignature(decorator); return signature ? getOrCreateTypeFromSignature(signature) : undefined; } 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 | undefined): 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 (canHaveSymbol(e) && 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); } if (isElementAccessExpression(e)) { const propType = checkExpressionCached(e.argumentExpression); if (!isTypeUsableAsPropertyName(propType)) { return undefined; } const lhsType = getTypeOfExpression(e.expression); return getPropertyOfType(lhsType, getPropertyNameFromType(propType)); } 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)) || (isPropertyDeclaration(decl) ? decl.initializer && getTypeOfExpression(binaryExpression.left) : undefined); } 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 (!canHaveSymbol(binaryExpression.left) || !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, /*nameNotFoundMessage*/ undefined, /*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) || decl === binaryExpression.left ? undefined : getTypeOfExpression(binaryExpression.left); } case AssignmentDeclarationKind.ExportsProperty: case AssignmentDeclarationKind.Prototype: case AssignmentDeclarationKind.PrototypeProperty: case AssignmentDeclarationKind.ModuleExports: let valueDeclaration: Declaration | undefined; if (kind !== AssignmentDeclarationKind.ModuleExports) { valueDeclaration = canHaveSymbol(binaryExpression.left) ? binaryExpression.left.symbol?.valueDeclaration : undefined; } 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, /*nameNotFoundMessage*/ 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, /*includeClassComputedPropertyName*/ 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).links.type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); } function isExcludedMappedPropertyName(constraint: Type, propertyNameType: Type): boolean { if (constraint.flags & TypeFlags.Conditional) { const type = constraint as ConditionalType; return !!(getReducedType(getTrueTypeFromConditionalType(type)).flags & TypeFlags.Never) && getActualTypeVariable(getFalseTypeFromConditionalType(type)) === getActualTypeVariable(type.checkType) && isTypeAssignableTo(propertyNameType, type.extendsType); } if (constraint.flags & TypeFlags.Intersection) { return some((constraint as IntersectionType).types, t => isExcludedMappedPropertyName(t, propertyNameType)); } return false; } function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) { return mapType(type, t => { if (t.flags & TypeFlags.Intersection) { let types: Type[] | undefined; let indexInfoCandidates: Type[] | undefined; let ignoreIndexInfos = false; for (const constituentType of (t as IntersectionType).types) { if (!(constituentType.flags & TypeFlags.Object)) { continue; } if (isGenericMappedType(constituentType) && getMappedTypeNameTypeKind(constituentType) !== MappedTypeNameTypeKind.Remapping) { const substitutedType = getIndexedMappedTypeSubstitutedTypeOfContextualType(constituentType, name, nameType); types = appendContextualPropertyTypeConstituent(types, substitutedType); continue; } const propertyType = getTypeOfConcretePropertyOfContextualType(constituentType, name); if (!propertyType) { if (!ignoreIndexInfos) { indexInfoCandidates = append(indexInfoCandidates, constituentType); } continue; } ignoreIndexInfos = true; indexInfoCandidates = undefined; types = appendContextualPropertyTypeConstituent(types, propertyType); } if (indexInfoCandidates) { for (const candidate of indexInfoCandidates) { const indexInfoType = getTypeFromIndexInfosOfContextualType(candidate, name, nameType); types = appendContextualPropertyTypeConstituent(types, indexInfoType); } } if (!types) { return; } if (types.length === 1) { return types[0]; } return getIntersectionType(types); } if (!(t.flags & TypeFlags.Object)) { return; } return isGenericMappedType(t) && getMappedTypeNameTypeKind(t) !== MappedTypeNameTypeKind.Remapping ? getIndexedMappedTypeSubstitutedTypeOfContextualType(t, name, nameType) : getTypeOfConcretePropertyOfContextualType(t, name) ?? getTypeFromIndexInfosOfContextualType(t, name, nameType); }, /*noReductions*/ true); } function appendContextualPropertyTypeConstituent(types: Type[] | undefined, type: Type | undefined) { // any doesn't provide any contextual information but could spoil the overall result by nullifying contextual information provided by other intersection constituents // so it gets replaced with `unknown` as `T & unknown` is just `T` and all types computed based on the contextual information provided by other constituens are still assignable to any return type ? append(types, type.flags & TypeFlags.Any ? unknownType : type) : types; } function getIndexedMappedTypeSubstitutedTypeOfContextualType(type: MappedType, name: __String, nameType: Type | undefined) { const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name)); const constraint = getConstraintTypeFromMappedType(type); // special case for conditional types pretending to be negated types if (type.nameType && isExcludedMappedPropertyName(type.nameType, propertyNameType) || isExcludedMappedPropertyName(constraint, propertyNameType)) { return; } const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint; if (!isTypeAssignableTo(propertyNameType, constraintOfConstraint)) { return; } return substituteIndexedMappedType(type, propertyNameType); } function getTypeOfConcretePropertyOfContextualType(type: Type, name: __String) { const prop = getPropertyOfType(type, name); if (!prop || isCircularMappedProperty(prop)) { return; } return removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional)); } function getTypeFromIndexInfosOfContextualType(type: Type, name: __String, nameType: Type | undefined) { if (isTupleType(type) && isNumericLiteralName(name) && +name >= 0) { const restType = getElementTypeOfSliceOfTupleType(type, type.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true); if (restType) { return restType; } } return findApplicableIndexInfo(getIndexInfosOfStructuredType(type), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type; } // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one // exists. Otherwise, it is the type of the string index signature in T, if one exists. function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags: ContextFlags | undefined): 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 | undefined) { const objectLiteral = element.parent as ObjectLiteralExpression; const propertyAssignmentType = isPropertyAssignment(element) && getContextualTypeForVariableLikeDeclaration(element, contextFlags); 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. const symbol = getSymbolOfDeclaration(element); return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType); } if (hasDynamicName(element)) { const name = getNameOfDeclaration(element); if (name && isComputedPropertyName(name)) { const exprType = checkExpression(name.expression); const propType = isTypeUsableAsPropertyName(exprType) && getTypeOfPropertyOfContextualType(type, getPropertyNameFromType(exprType)); if (propType) { return propType; } } } 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; } function getSpreadIndices(elements: readonly Node[]) { let first, last; for (let i = 0; i { if (isTupleType(t)) { // If index is before any spread element and within the fixed part of the contextual tuple type, return // the type of the contextual tuple element. if ((firstSpreadIndex === undefined || index lastSpreadIndex) ? length - index : 0; const fixedEndLength = offset > 0 && (t.target.combinedFlags & ElementFlags.Variable) ? getEndElementCount(t.target, ElementFlags.Fixed) : 0; // If the offset is within the ending fixed part of the contextual tuple type, return the type of the contextual // tuple element. if (offset > 0 && offset { if (isArrayLikeType(t)) { return getIndexedAccessType(t, getNumberLiteralType(childIndex)); } else { return t; } }, /*noReductions*/ true)); } function getContextualTypeForJsxExpression(node: JsxExpression, contextFlags: ContextFlags | undefined): Type | undefined { const exprParent = node.parent; return isJsxAttributeLike(exprParent) ? getContextualType(node, contextFlags) : isJsxElement(exprParent) ? getContextualTypeForChildJsxExpression(exprParent, node, contextFlags) : undefined; } function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute, contextFlags: ContextFlags | undefined): 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, contextFlags); if (!attributesType || isTypeAny(attributesType)) { return undefined; } return getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name)); } else { return getContextualType(attribute.parent, contextFlags); } } // 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.TemplateExpression: 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) { const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; return getCachedType(key) ?? setCachedType( key, getMatchingUnionConstituentForObjectLiteral(contextualType, node) ?? discriminateTypeByDiscriminableItems( contextualType, concatenate( map( filter(node.properties, (p): p is PropertyAssignment | ShorthandPropertyAssignment => { if (!p.symbol) { return false; } if (p.kind === SyntaxKind.PropertyAssignment) { return isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName); } if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { return isDiscriminantProperty(contextualType, p.symbol.escapedName); } return false; }), prop => ([() => getContextFreeTypeOfExpression(prop.kind === SyntaxKind.PropertyAssignment ? prop.initializer : prop.name), prop.symbol.escapedName] as const), ), 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 const, ), ), isTypeAssignableTo, ), ); } function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; const cached = getCachedType(key); if (cached) return cached; const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); return setCachedType( key, 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 const), ), map( filter(getPropertiesOfType(contextualType), s => { if (!(s.flags & SymbolFlags.Optional) || !node?.symbol?.members) { return false; } const element = node.parent.parent; if (s.escapedName === jsxChildrenPropertyName && isJsxElement(element) && getSemanticJsxChildren(element.children).length) { return false; } return !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName); }), s => [() => undefinedType, s.escapedName] as const, ), ), isTypeAssignableTo, ), ); } // 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 | undefined): 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, // When obtaining apparent type of *contextual* type we don't want to get apparent type of mapped types. // That would evaluate mapped types with array or tuple type constraints too eagerly // and thus it would prevent `getTypeOfPropertyOfContextualType` from obtaining per-position contextual type for elements of array literal expressions. // Apparent type of other mapped types is already the mapped type itself so we can just avoid calling `getApparentType` here for all mapped types. t => getObjectFlags(t) & ObjectFlags.Mapped ? t : getApparentType(t), /*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 | undefined): Type | undefined { if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { const inferenceContext = getInferenceContext(node); // If no inferences have been made, and none of the type parameters for which we are inferring // specify default types, nothing is gained from instantiating as type parameters would just be // replaced with their constraints similar to the apparent type. if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) { // 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. return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); } if (inferenceContext?.returnMapper) { // For other purposes (e.g. determining whether to produce literal types) we only // incorporate inferences made from the return type in a function call. We remove // the 'boolean' type from the contextual type such that contextually typed boolean // literals actually end up widening to 'boolean' (see #48363). const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ? filterType(type, t => t !== regularFalseType && t !== regularTrueType) : type; } } 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 | undefined): Type | undefined { if (node.flags & NodeFlags.InWithStatement) { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; } // Cached contextual types are obtained with no ContextFlags, so we can only consult them for // requests with no ContextFlags. const index = findContextualNode(node, /*includeCaches*/ !contextFlags); if (index >= 0) { return contextualTypes[index]; } 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, contextFlags); case SyntaxKind.YieldExpression: return getContextualTypeForYieldOperand(parent as YieldExpression, contextFlags); case SyntaxKind.AwaitExpression: return getContextualTypeForAwaitOperand(parent as AwaitExpression, contextFlags); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return getContextualTypeForArgument(parent as CallExpression | NewExpression | Decorator, node); case SyntaxKind.Decorator: return getContextualTypeForDecorator(parent as Decorator); case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: return isConstTypeReference((parent as AssertionExpression).type) ? getContextualType(parent as AssertionExpression, contextFlags) : 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); const elementIndex = indexOfNode(arrayLiteral.elements, node); const spreadIndices = getNodeLinks(arrayLiteral).spreadIndices ??= getSpreadIndices(arrayLiteral.elements); return getContextualTypeForElementExpression(type, elementIndex, arrayLiteral.elements.length, spreadIndices.first, spreadIndices.last); } 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: { if (isInJSFile(parent)) { if (isJSDocSatisfiesExpression(parent)) { return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent)); } // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. const typeTag = getJSDocTypeTag(parent); if (typeTag && !isConstTypeReference(typeTag.typeExpression.type)) { return getTypeFromTypeNode(typeTag.typeExpression.type); } } return getContextualType(parent as ParenthesizedExpression, contextFlags); } case SyntaxKind.NonNullExpression: return getContextualType(parent as NonNullExpression, contextFlags); case SyntaxKind.SatisfiesExpression: return getTypeFromTypeNode((parent as SatisfiesExpression).type); case SyntaxKind.ExportAssignment: return tryGetTypeFromEffectiveTypeNode(parent as ExportAssignment); case SyntaxKind.JsxExpression: return getContextualTypeForJsxExpression(parent as JsxExpression, contextFlags); case SyntaxKind.JsxAttribute: case SyntaxKind.JsxSpreadAttribute: return getContextualTypeForJsxAttribute(parent as JsxAttribute | JsxSpreadAttribute, contextFlags); case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: return getContextualJsxElementAttributesType(parent as JsxOpeningLikeElement, contextFlags); case SyntaxKind.ImportAttribute: return getContextualImportAttributeType(parent as ImportAttribute); } return undefined; } function pushCachedContextualType(node: Expression) { pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined), /*isCache*/ true); } function pushContextualType(node: Expression, type: Type | undefined, isCache: boolean) { contextualTypeNodes[contextualTypeCount] = node; contextualTypes[contextualTypeCount] = type; contextualIsCache[contextualTypeCount] = isCache; contextualTypeCount++; } function popContextualType() { contextualTypeCount--; // Clear out the popped element's referenced objects. contextualTypeNodes[contextualTypeCount] = undefined!; contextualTypes[contextualTypeCount] = undefined; contextualIsCache[contextualTypeCount] = undefined!; } function findContextualNode(node: Node, includeCaches: boolean) { for (let i = contextualTypeCount - 1; i >= 0; i--) { if (node === contextualTypeNodes[i] && (includeCaches || !contextualIsCache[i])) { return i; } } return -1; } function pushInferenceContext(node: Node, inferenceContext: InferenceContext | undefined) { inferenceContextNodes[inferenceContextCount] = node; inferenceContexts[inferenceContextCount] = inferenceContext; inferenceContextCount++; } function popInferenceContext() { inferenceContextCount--; inferenceContextNodes[inferenceContextCount] = undefined!; inferenceContexts[inferenceContextCount] = undefined; } function getInferenceContext(node: Node) { for (let i = inferenceContextCount - 1; i >= 0; i--) { if (isNodeDescendantOf(node, inferenceContextNodes[i])) { return inferenceContexts[i]; } } } function pushActiveMapper(mapper: TypeMapper) { activeTypeMappers[activeTypeMappersCount] = mapper; activeTypeMappersCaches[activeTypeMappersCount] ??= new Map(); activeTypeMappersCount++; } function popActiveMapper() { activeTypeMappersCount--; // Clear out the popped element's referenced objects. activeTypeMappers[activeTypeMappersCount] = undefined!; activeTypeMappersCaches[activeTypeMappersCount].clear(); } function findActiveMapper(mapper: TypeMapper) { for (let i = activeTypeMappersCount - 1; i >= 0; i--) { if (mapper === activeTypeMappers[i]) { return i; } } return -1; } function clearActiveMapperCaches() { for (let i = activeTypeMappersCount - 1; i >= 0; i--) { activeTypeMappersCaches[i].clear(); } } function getContextualImportAttributeType(node: ImportAttribute) { return getTypeOfPropertyOfContextualType(getGlobalImportAttributesType(/*reportErrors*/ false), getNameFromImportAttribute(node)); } function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) { if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) { const index = findContextualNode(node.parent, /*includeCaches*/ !contextFlags); if (index >= 0) { // 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 contextualTypes[index]; } } return getContextualTypeForArgumentAtIndex(node, 0); } function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxCallLike) { return isJsxOpeningFragment(node) || getJsxReferenceKind(node) !== JsxReferenceKind.Component ? getJsxPropsTypeFromCallSignature(signature, node) : getJsxPropsTypeFromClassType(signature, node); } function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxCallLike) { 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: JsxCallLike) { if (isJsxOpeningFragment(context)) return getJSXFragmentType(context); if (isJsxIntrinsicTagName(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: JsxCallLike, ns: Symbol, attributesType: Type) { const managedSym = getJsxLibraryManagedAttributes(ns); if (managedSym) { const ctorType = getStaticTypeOfReferencedJsxConstructor(context); const result = instantiateAliasOrInterfaceWithDefaults(managedSym, isInJSFile(context), ctorType, attributesType); if (result) { return result; } } 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